From 8c198abcae56676284ddc004900680f5a2b0e582 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Mon, 15 Dec 2014 14:10:42 +0100 Subject: [PATCH 001/264] Localized the sailing clusters --- .../Sailing_StringMessages_de.properties | 16 ++++++++++- .../Sailing_StringMessages_en.properties | 16 ++++++++++- .../datamining/SailingClusterGroups.java | 28 +++++++++---------- .../datamining/data/HasGPSFixContext.java | 4 +-- .../impl/data/GPSFixWithContext.java | 6 ++-- .../sse/datamining/functions/Function.java | 2 +- .../ConcatenatingCompoundFunction.java | 8 +++--- .../functions/MethodWrappingFunction.java | 4 +-- 8 files changed, 57 insertions(+), 27 deletions(-) diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties index dd8b19be160..ff7916e9925 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties @@ -21,4 +21,18 @@ LegOfCompetitor=Schenkel eines Teilnehmers GpsFix=GPS-Fix Speed=Geschwindigkeit InKnots=in Knoten -WindStrength=Windstärke \ No newline at end of file +WindStrengthInBeaufort=Windstärke in Beaufort +BftClusterGroup=Windstärke in Beaufort +Bft0=Windstille +Bft1=leiser Zug +Bft2=leichte Brise +Bft3=schwacher Wind +Bft4=mäßiger Wind +Bft5=frischer Wind +Bft6=starker Wind +Bft7=steifer Wind +Bft8=stürmischer Wind +Bft9=Sturm +Bft10=schwerer Sturm +Bft11=orkanartiger Sturm +Bft12=Orkan \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties index 62e92c3db80..f7bc61b131e 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties @@ -21,4 +21,18 @@ RegattaLeaderboard=Regatta Leaderboard Leg=Leg LegOfCompetitor=Leg of competitor GpsFix=GPS-Fix -WindStrength=Wind Strength \ No newline at end of file +WindStrengthInBeaufort=Wind Strength in Beaufort +BftClusterGroup=Wind Strength in Beaufort +Bft0=Calm +Bft1=Light air +Bft2=Light breeze +Bft3=Gentle breeze +Bft4=Moderate breeze +Bft5=Fresh breeze +Bft6=Strong breeze +Bft7=High wind +Bft8=Gale +Bft9=Strong gale +Bft10=Storm +Bft11=Violent storm +Bft12=Hurricane \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java index ee2336925f8..4330ba31c47 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java @@ -28,79 +28,79 @@ public class SailingClusterGroups { ClusterBoundary lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); Speed upperBoundWindSpeed = new KnotSpeedImpl(1.0); ClusterBoundary upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Calm", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft0", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(1.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(4.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Light air", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft1", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(4.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(7.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Light breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft2", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(7.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(11.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Gentle breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft3", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(11.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(16.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Moderate breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft4", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(16.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(22.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Fresh breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft5", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(22.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(28.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Strong breeze", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft6", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(28.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(34.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("High wind", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft7", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(34.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(41.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Gale", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft8", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(41.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(48.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Strong gale", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft9", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(48.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(56.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Storm", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft10", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(56.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); upperBoundWindSpeed = new KnotSpeedImpl(64.0); upperBound = new ComparatorClusterBoundary(comparableComparator, upperBoundWindSpeed, ComparisonStrategy.LOWER_THAN); - clusters.add(new ClusterWithLowerAndUpperBoundaries("Violent Storm", lowerBound, upperBound)); + clusters.add(new ClusterWithLowerAndUpperBoundaries("Bft11", lowerBound, upperBound)); lowerBoundWindSpeed = new KnotSpeedImpl(64.0); lowerBound = new ComparatorClusterBoundary(comparableComparator, lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); - clusters.add(new ClusterWithSingleBoundary("Hurricane", lowerBound)); + clusters.add(new ClusterWithSingleBoundary("Bft12", lowerBound)); - windStrengthInBeaufortCluster = new FixClusterGroup("Wind Strength", clusters); + windStrengthInBeaufortCluster = new FixClusterGroup("BftClusterGroup", clusters); } public ClusterGroup getWindStrengthInBeaufortCluster() { diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java index f901b18c9ed..d83e690a332 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java @@ -13,7 +13,7 @@ public interface HasGPSFixContext { @Connector public GPSFixMoving getGPSFix(); - @Dimension(messageKey="WindStrength", ordinal=10) - public ClusterDTO getWindStrength(); + @Dimension(messageKey="WindStrengthInBeaufort", ordinal=10) + public ClusterDTO getWindStrengthAsBeaufortCluster(); } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java index fe1d4a2adc4..ac5e68d8600 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java @@ -1,5 +1,7 @@ package com.sap.sailing.datamining.impl.data; +import java.util.Locale; + import com.sap.sailing.datamining.Activator; import com.sap.sailing.datamining.data.HasGPSFixContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; @@ -31,10 +33,10 @@ public class GPSFixWithContext implements HasGPSFixContext { } @Override - public ClusterDTO getWindStrength() { + public ClusterDTO getWindStrengthAsBeaufortCluster() { Wind wind = getWind(); Cluster cluster = Activator.getDefault().getClusterGroups().getWindStrengthInBeaufortCluster().getClusterFor(wind); - return new ClusterDTO(cluster.toString()); + return new ClusterDTO(cluster.getAsLocalizedString(Locale.ENGLISH, Activator.getDefault().getStringMessages())); } private Wind getWind() { diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java index 17c3017cf64..63a4fcaebf3 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java @@ -20,7 +20,7 @@ public interface Function { * @return true, if the method {@link #getLocalizedName(Locale, DataMiningStringMessages) getLocalizedName} would return something * other than the method {@link #getSimpleName()}. */ - public boolean isLocatable(); + public boolean isLocalizable(); public String getLocalizedName(Locale locale, DataMiningStringMessages stringMessages); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java index 7e5b1a6c93e..f237ab96793 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java @@ -90,7 +90,7 @@ public class ConcatenatingCompoundFunction extends AbstractFunction< if (name != null && !name.isEmpty()) { return name; } - if (!isLocatable()) { + if (!isLocalizable()) { return getSimpleName(); } @@ -98,9 +98,9 @@ public class ConcatenatingCompoundFunction extends AbstractFunction< } @Override - public boolean isLocatable() { + public boolean isLocalizable() { for (Function function : functions) { - if (function.isLocatable()) { + if (function.isLocalizable()) { return true; } } @@ -111,7 +111,7 @@ public class ConcatenatingCompoundFunction extends AbstractFunction< StringBuilder builder = new StringBuilder(); boolean first = true; for (Function function : functions) { - if (function.isLocatable()) { + if (function.isLocalizable()) { if (!first) { builder.append(LOCALIZED_NAME_CHAIN_CONNECTOR); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java index ca10e460fc2..67eb4c624db 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java @@ -116,14 +116,14 @@ public class MethodWrappingFunction extends AbstractFunction Date: Mon, 15 Dec 2014 14:13:16 +0100 Subject: [PATCH 002/264] Adjusted the sailing retriever names --- .../SailingDataRetrieverChainDefinitions.java | 16 ++++++++-------- ...=> RegattaLeaderboardRetrievalProcessor.java} | 4 ++-- ...rackedLegOfCompetitorRetrievalProcessor.java} | 4 ++-- ...or.java => TrackedLegRetrievalProcessor.java} | 4 ++-- ...r.java => TrackedRaceRetrievalProcessor.java} | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) rename java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/{RegattaLeaderboardFilteringRetrievalProcessor.java => RegattaLeaderboardRetrievalProcessor.java} (88%) rename java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/{TrackedLegOfCompetitorFilteringRetrievalProcessor.java => TrackedLegOfCompetitorRetrievalProcessor.java} (90%) rename java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/{TrackedLegFilteringRetrievalProcessor.java => TrackedLegRetrievalProcessor.java} (91%) rename java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/{TrackedRaceFilteringRetrievalProcessor.java => TrackedRaceRetrievalProcessor.java} (92%) diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java index 0b0d3cb35ce..478ac9a25ad 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java @@ -9,10 +9,10 @@ import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor; import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.RegattaLeaderboardFilteringRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.TrackedLegFilteringRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorFilteringRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.TrackedRaceFilteringRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.RegattaLeaderboardRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.server.RacingEventService; @@ -33,18 +33,18 @@ public class SailingDataRetrieverChainDefinitions { Class> leaderboardGroupRetrieverType = (Class>) (Class) LeaderboardGroupRetrievalProcessor.class; legOfCompetitorRetrieverChainDefinition.startWith(leaderboardGroupRetrieverType, LeaderboardGroup.class, "LeaderboardGroup"); @SuppressWarnings("unchecked") - Class> regattaLeaderboardRetrieverType = (Class>) (Class) RegattaLeaderboardFilteringRetrievalProcessor.class; + Class> regattaLeaderboardRetrieverType = (Class>) (Class) RegattaLeaderboardRetrievalProcessor.class; legOfCompetitorRetrieverChainDefinition.addAfter(leaderboardGroupRetrieverType, regattaLeaderboardRetrieverType, RegattaLeaderboard.class, "RegattaLeaderboard"); @SuppressWarnings("unchecked") - Class> raceRetrieverType = (Class>) (Class) TrackedRaceFilteringRetrievalProcessor.class; + Class> raceRetrieverType = (Class>) (Class) TrackedRaceRetrievalProcessor.class; legOfCompetitorRetrieverChainDefinition.addAfter(regattaLeaderboardRetrieverType, raceRetrieverType, HasTrackedRaceContext.class, "Race"); @SuppressWarnings("unchecked") - Class> legRetrieverType = (Class>) (Class) TrackedLegFilteringRetrievalProcessor.class; + Class> legRetrieverType = (Class>) (Class) TrackedLegRetrievalProcessor.class; legOfCompetitorRetrieverChainDefinition.addAfter(raceRetrieverType, legRetrieverType, HasTrackedLegContext.class, "Leg"); @SuppressWarnings("unchecked") - Class> legOfCompetitorRetrieverType = (Class>) (Class) TrackedLegOfCompetitorFilteringRetrievalProcessor.class; + Class> legOfCompetitorRetrieverType = (Class>) (Class) TrackedLegOfCompetitorRetrievalProcessor.class; legOfCompetitorRetrieverChainDefinition.endWith(legRetrieverType, legOfCompetitorRetrieverType, HasTrackedLegOfCompetitorContext.class, "LegOfCompetitor"); dataRetrieverChainDefinitions.add(legOfCompetitorRetrieverChainDefinition); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardRetrievalProcessor.java similarity index 88% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardFilteringRetrievalProcessor.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardRetrievalProcessor.java index 6dba1811dba..89361bc48a3 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/RegattaLeaderboardRetrievalProcessor.java @@ -10,10 +10,10 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; -public class RegattaLeaderboardFilteringRetrievalProcessor extends +public class RegattaLeaderboardRetrievalProcessor extends AbstractSimpleRetrievalProcessor { - public RegattaLeaderboardFilteringRetrievalProcessor(ExecutorService executor, + public RegattaLeaderboardRetrievalProcessor(ExecutorService executor, Collection> resultReceivers) { super(LeaderboardGroup.class, RegattaLeaderboard.class, executor, resultReceivers); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java similarity index 90% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorFilteringRetrievalProcessor.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java index c412b8d316c..8c25a0029cd 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegOfCompetitorRetrievalProcessor.java @@ -11,10 +11,10 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; -public class TrackedLegOfCompetitorFilteringRetrievalProcessor extends +public class TrackedLegOfCompetitorRetrievalProcessor extends AbstractSimpleRetrievalProcessor { - public TrackedLegOfCompetitorFilteringRetrievalProcessor(ExecutorService executor, + public TrackedLegOfCompetitorRetrievalProcessor(ExecutorService executor, Collection> resultReceivers) { super(HasTrackedLegContext.class, HasTrackedLegOfCompetitorContext.class, executor, resultReceivers); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java similarity index 91% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegFilteringRetrievalProcessor.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java index cffac84007d..2e61e0051a3 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedLegRetrievalProcessor.java @@ -11,10 +11,10 @@ import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; -public class TrackedLegFilteringRetrievalProcessor extends +public class TrackedLegRetrievalProcessor extends AbstractSimpleRetrievalProcessor { - public TrackedLegFilteringRetrievalProcessor(ExecutorService executor, + public TrackedLegRetrievalProcessor(ExecutorService executor, Collection> resultReceivers) { super(HasTrackedRaceContext.class, HasTrackedLegContext.class, executor, resultReceivers); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceFilteringRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java similarity index 92% rename from java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceFilteringRetrievalProcessor.java rename to java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java index ed5617988ee..7800527efda 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceFilteringRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/TrackedRaceRetrievalProcessor.java @@ -14,10 +14,10 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; -public class TrackedRaceFilteringRetrievalProcessor extends +public class TrackedRaceRetrievalProcessor extends AbstractSimpleRetrievalProcessor { - public TrackedRaceFilteringRetrievalProcessor(ExecutorService executor, + public TrackedRaceRetrievalProcessor(ExecutorService executor, Collection> resultReceivers) { super(Leaderboard.class, HasTrackedRaceContext.class, executor, resultReceivers); } From dca9f1b1725db9e2451145cfafc5149a48faef25 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Mon, 15 Dec 2014 15:59:24 +0100 Subject: [PATCH 003/264] Introduced a ParameterProvider for the Functions --- .../TestMethodWrappingFunctionInvocation.java | 19 +++++++-- .../criterias/TestMethodFilterCriterias.java | 6 +-- .../sse/datamining/functions/Function.java | 5 ++- .../functions/ParameterProvider.java | 11 +++++ ...mensionsValueNestingGroupingProcessor.java | 4 +- .../ConcatenatingCompoundFunction.java | 13 +++--- .../functions/MethodWrappingFunction.java | 9 +++- .../impl/functions/NullParameterProvider.java | 12 ++++++ .../functions/SimpleFunctionRegistry.java | 41 ++++++------------- .../StringMessagesParameterProvider.java | 21 ++++++++++ .../IsDeclaringTypeFilterCriterion.java | 2 +- ...MethodIsValidConnectorFilterCriterion.java | 16 ++++++++ ...ethodIsValidDimensionFilterCriterion.java} | 2 +- ...ValidExternalFunctionFilterCriterion.java} | 2 +- ...ethodIsValidStatisticFilterCriterion.java} | 5 ++- 15 files changed, 117 insertions(+), 51 deletions(-) create mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java create mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/NullParameterProvider.java create mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/StringMessagesParameterProvider.java create mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidConnectorFilterCriterion.java rename java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/{MethodIsCorrectDimensionFilterCriterion.java => MethodIsValidDimensionFilterCriterion.java} (78%) rename java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/{MethodIsCorrectExternalFunctionFilterCriterion.java => MethodIsValidExternalFunctionFilterCriterion.java} (72%) rename java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/{MethodIsCorrectStatisticFilterCriterion.java => MethodIsValidStatisticFilterCriterion.java} (50%) diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java index 5abb1c7f064..b6cdbe52b4d 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java @@ -8,6 +8,7 @@ import org.junit.Before; import org.junit.Test; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.test.functions.test_classes.DataTypeWithContext; import com.sap.sse.datamining.test.functions.test_classes.DataTypeWithContextImpl; import com.sap.sse.datamining.test.functions.test_classes.SimpleClassWithMarkedMethods; @@ -33,14 +34,26 @@ public class TestMethodWrappingFunctionInvocation { @Test public void testInvocationWithParameters() { SimpleClassWithMarkedMethods instance = new SimpleClassWithMarkedMethods(); - int valueToIncrement = 10; - assertThat(increment.tryToInvoke(instance, valueToIncrement), is(instance.increment(valueToIncrement))); + final int valueToIncrement = 10; + ParameterProvider parameterProvider = new ParameterProvider() { + @Override + public Object[] getParameters() { + return new Object[] {valueToIncrement}; + } + }; + assertThat(increment.tryToInvoke(instance, parameterProvider), is(instance.increment(valueToIncrement))); } @Test public void testInvocationWithWrongParameters() { DataTypeWithContext dataEntry = new DataTypeWithContextImpl("Regatta Name", "Race Name", 7); - assertThat(getRegattaName.tryToInvoke(dataEntry, "Wrong Parameter"), is(nullValue())); + ParameterProvider parameterProvider = new ParameterProvider() { + @Override + public Object[] getParameters() { + return new Object[] {"Wrong Parameter"}; + } + }; + assertThat(getRegattaName.tryToInvoke(dataEntry, parameterProvider), is(nullValue())); SimpleClassWithMarkedMethods instance = new SimpleClassWithMarkedMethods(); assertThat(increment.tryToInvoke(instance), is(nullValue())); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/criterias/TestMethodFilterCriterias.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/criterias/TestMethodFilterCriterias.java index 863c2d887c3..c8a2df4feee 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/criterias/TestMethodFilterCriterias.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/criterias/TestMethodFilterCriterias.java @@ -14,7 +14,7 @@ public class TestMethodFilterCriterias { @Test public void testMethodIsCorrectDimensionFilterCriteria() { - FilterCriterion filterCriteria = new MethodIsCorrectDimensionFilterCriterion(); + FilterCriterion filterCriteria = new MethodIsValidDimensionFilterCriterion(); Method dimension = FunctionTestsUtil.getMethodFromSimpleClassWithMarkedMethod("dimension"); assertThat(filterCriteria.matches(dimension), is(true)); @@ -28,7 +28,7 @@ public class TestMethodFilterCriterias { @Test public void testMethodIsCorrectSideEffectFreeValueFilterCriteria() { - FilterCriterion filterCriteria = new MethodIsCorrectStatisticFilterCriterion(); + FilterCriterion filterCriteria = new MethodIsValidStatisticFilterCriterion(); Method sideEffectFreeValue = FunctionTestsUtil.getMethodFromSimpleClassWithMarkedMethod("sideEffectFreeValue"); assertThat(filterCriteria.matches(sideEffectFreeValue), is(true)); @@ -39,7 +39,7 @@ public class TestMethodFilterCriterias { @Test public void testMethodIsCorrectExternalLibraryFunctionFilterCriteria() { - FilterCriterion filterCriteria = new MethodIsCorrectExternalFunctionFilterCriterion(); + FilterCriterion filterCriteria = new MethodIsValidExternalFunctionFilterCriterion(); Method foo = FunctionTestsUtil.getMethodFromExternalLibraryClass("foo"); assertThat(filterCriteria.matches(foo), is(true)); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java index 63a4fcaebf3..1376f28f28f 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/Function.java @@ -33,12 +33,13 @@ public interface Function { public ReturnType tryToInvoke(Object instance); /** - * Tries to invoke the function for the given instance and the given parameters. + * Tries to invoke the function for the given instance and the parameters provided + * by the given {@link ParameterProvider}. * * @return The result of the function or null, if an {@link InvocationTargetException}, * {@link IllegalAccessException} or {@link IllegalArgumentException} was thrown. */ - public ReturnType tryToInvoke(Object instance, Object... parameters); + public ReturnType tryToInvoke(Object instance, ParameterProvider parameterProvider); public Unit getResultUnit(); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java new file mode 100644 index 00000000000..32d37a9487b --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java @@ -0,0 +1,11 @@ +package com.sap.sse.datamining.functions; + +import com.sap.sse.datamining.impl.functions.NullParameterProvider; + +public interface ParameterProvider { + + public static final ParameterProvider NULL = new NullParameterProvider(); + + public Object[] getParameters(); + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java index 2ae8838b40e..a3462632b5d 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java @@ -27,8 +27,8 @@ public class ParallelMultiDimensionsValueNestingGroupingProcessor super(dataType, executor, resultReceivers, dimensions); } - protected GroupKey createGroupKeyFor(DataType input, Function mainDimension) { - Object keyValue = mainDimension.tryToInvoke(input); + protected GroupKey createGroupKeyFor(DataType input, Function dimension) { + Object keyValue = dimension.tryToInvoke(input); return new GenericGroupKey(keyValue); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java index f237ab96793..bdea17afa48 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/ConcatenatingCompoundFunction.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Locale; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.shared.Unit; @@ -124,16 +125,16 @@ public class ConcatenatingCompoundFunction extends AbstractFunction< @Override public ReturnType tryToInvoke(Object instance) { - return tryToInvoke(instance, new Object[0]); + return tryToInvoke(instance, ParameterProvider.NULL); } - - @SuppressWarnings("unchecked") // The cast has to work. The types were checked in the constructor. + + @SuppressWarnings("unchecked") @Override - public ReturnType tryToInvoke(Object instance, Object... parameters) { + public ReturnType tryToInvoke(Object instance, ParameterProvider parameterProvider) { Iterator> functionsIterator = functions.iterator(); - Object result = functionsIterator.next().tryToInvoke(instance, parameters); + Object result = functionsIterator.next().tryToInvoke(instance, parameterProvider); while (functionsIterator.hasNext()) { - Function function = (Function) functionsIterator.next(); + Function function = functionsIterator.next(); result = function.tryToInvoke(result); if (result == null) { return null; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java index 67eb4c624db..122cd6d95b7 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/MethodWrappingFunction.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.Locale; import java.util.logging.Level; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.shared.Unit; import com.sap.sse.datamining.shared.annotations.Connector; @@ -81,9 +82,13 @@ public class MethodWrappingFunction extends AbstractFunction isValidDimension = new MethodIsValidDimensionFilterCriterion(); + private final FilterCriterion isValidStatistic = new MethodIsValidStatisticFilterCriterion(); + private final FilterCriterion isValidConnector = new MethodIsValidConnectorFilterCriterion(); + private final FilterCriterion isValidExternalFunction = new MethodIsValidExternalFunctionFilterCriterion(); private final FunctionFactory functionFactory; @@ -53,12 +60,12 @@ public class SimpleFunctionRegistry implements FunctionRegistry { private void scanInternalClass(Class internalClass, List> previousFunctions) { for (Method method : internalClass.getMethods()) { - if (isValidDimension(method) || isValidStatistic(method)) { + if (isValidDimension.matches(method) || isValidStatistic.matches(method)) { registerFunction(previousFunctions, method); continue; } - if (isConnector(method)) { + if (isValidConnector.matches(method)) { handleConnectorMethod(method, previousFunctions); } } @@ -101,29 +108,11 @@ public class SimpleFunctionRegistry implements FunctionRegistry { scanInternalClass(returnType, previousFunctionsClone); } - private boolean isValidDimension(Method method) { - return method.getAnnotation(Dimension.class) != null && - !method.getReturnType().equals(Void.TYPE) && - method.getParameterTypes().length == 0; - } - - private boolean isValidStatistic(Method method) { - return method.getAnnotation(Statistic.class) != null && - !method.getReturnType().equals(Void.TYPE) && - method.getParameterTypes().length == 0; - } - - private boolean isConnector(Method method) { - return method.getAnnotation(Connector.class) != null && - !method.getReturnType().equals(Void.TYPE) && - method.getParameterTypes().length == 0; - } - @Override public void registerAllWithExternalFunctionPolicy(Iterable> externalClassesToScan) { for (Class externalClass : externalClassesToScan) { for (Method method : externalClass.getMethods()) { - if (isValidExternalFunction(method)) { + if (isValidExternalFunction.matches(method)) { Function function = functionFactory.createMethodWrappingFunction(method); addExternalFunction(function); } @@ -139,10 +128,6 @@ public class SimpleFunctionRegistry implements FunctionRegistry { externalFunctions.get(declaringType).add(function); } - private boolean isValidExternalFunction(Method method) { - return !method.getReturnType().equals(Void.TYPE) && !method.getDeclaringClass().equals(Object.class); - } - @Override public void unregisterAllFunctionsOf(Iterable> classesToUnregister) { for (Class classToUnregister : classesToUnregister) { diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/StringMessagesParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/StringMessagesParameterProvider.java new file mode 100644 index 00000000000..2ba3b04d21f --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/StringMessagesParameterProvider.java @@ -0,0 +1,21 @@ +package com.sap.sse.datamining.impl.functions; + +import java.util.Locale; + +import com.sap.sse.datamining.functions.ParameterProvider; +import com.sap.sse.datamining.i18n.DataMiningStringMessages; + +public class StringMessagesParameterProvider implements ParameterProvider { + + private final Object[] parameters; + + public StringMessagesParameterProvider(Locale locale, DataMiningStringMessages stringMessages) { + parameters = new Object[] {locale, stringMessages}; + } + + @Override + public Object[] getParameters() { + return parameters; + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/IsDeclaringTypeFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/IsDeclaringTypeFilterCriterion.java index adfdc8d6060..4e95830b116 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/IsDeclaringTypeFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/IsDeclaringTypeFilterCriterion.java @@ -53,7 +53,7 @@ public class IsDeclaringTypeFilterCriterion extends AbstractFunctionFilterCriter return false; } - public boolean isDeclaringTypeMatching(Function function, Class expectingType) { + private boolean isDeclaringTypeMatching(Function function, Class expectingType) { return function.getDeclaringType().equals(expectingType); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidConnectorFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidConnectorFilterCriterion.java new file mode 100644 index 00000000000..852866da46a --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidConnectorFilterCriterion.java @@ -0,0 +1,16 @@ +package com.sap.sse.datamining.impl.functions.criterias; + +import java.lang.reflect.Method; + +import com.sap.sse.datamining.shared.annotations.Connector; + +public class MethodIsValidConnectorFilterCriterion extends AbstractMethodFilterCriterion { + + @Override + public boolean matches(Method method) { + return method.getAnnotation(Connector.class) != null && + !method.getReturnType().equals(Void.TYPE) && + method.getParameterTypes().length == 0; + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectDimensionFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidDimensionFilterCriterion.java similarity index 78% rename from java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectDimensionFilterCriterion.java rename to java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidDimensionFilterCriterion.java index f3c054f4f8a..380cde12d29 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectDimensionFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidDimensionFilterCriterion.java @@ -4,7 +4,7 @@ import java.lang.reflect.Method; import com.sap.sse.datamining.shared.annotations.Dimension; -public class MethodIsCorrectDimensionFilterCriterion extends AbstractMethodFilterCriterion { +public class MethodIsValidDimensionFilterCriterion extends AbstractMethodFilterCriterion { @Override public boolean matches(Method method) { diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectExternalFunctionFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidExternalFunctionFilterCriterion.java similarity index 72% rename from java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectExternalFunctionFilterCriterion.java rename to java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidExternalFunctionFilterCriterion.java index af7923a68d8..239507361e3 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectExternalFunctionFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidExternalFunctionFilterCriterion.java @@ -2,7 +2,7 @@ package com.sap.sse.datamining.impl.functions.criterias; import java.lang.reflect.Method; -public class MethodIsCorrectExternalFunctionFilterCriterion extends AbstractMethodFilterCriterion { +public class MethodIsValidExternalFunctionFilterCriterion extends AbstractMethodFilterCriterion { @Override public boolean matches(Method method) { diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectStatisticFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidStatisticFilterCriterion.java similarity index 50% rename from java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectStatisticFilterCriterion.java rename to java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidStatisticFilterCriterion.java index 9223e3acc65..88cfece2c6a 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsCorrectStatisticFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/criterias/MethodIsValidStatisticFilterCriterion.java @@ -4,11 +4,12 @@ import java.lang.reflect.Method; import com.sap.sse.datamining.shared.annotations.Statistic; -public class MethodIsCorrectStatisticFilterCriterion extends AbstractMethodFilterCriterion { +public class MethodIsValidStatisticFilterCriterion extends AbstractMethodFilterCriterion { @Override public boolean matches(Method method) { - return method.getAnnotation(Statistic.class) != null; + return method.getAnnotation(Statistic.class) != null && + !method.getReturnType().equals(Void.TYPE); } } From 322621f2d1268bade2cf0d51c48a1933eb5a7d2c Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Tue, 16 Dec 2014 09:38:54 +0100 Subject: [PATCH 004/264] The function related processors are now usable with ParameterProviders --- ...llelMultiDimensionalGroupingProcessor.java | 11 ++++--- .../factories/ProcessorFactory.java | 12 +++++++- ...tiDimensionalNestingGroupingProcessor.java | 30 ++++++++++--------- .../ParallelByDimensionGroupingProcessor.java | 10 ++++--- ...oupedElementsValueExtractionProcessor.java | 13 ++++++-- ...mensionsValueNestingGroupingProcessor.java | 10 ++++--- .../NullaryFunctionValuesFilterCriterion.java | 15 +++++++--- 7 files changed, 68 insertions(+), 33 deletions(-) diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java index a43392cf233..8467f9f3066 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java @@ -12,6 +12,7 @@ import org.junit.Before; import org.junit.Test; import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.factories.ProcessorFactory; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.impl.functions.MethodWrappingFunction; import com.sap.sse.datamining.shared.GroupKey; @@ -25,6 +26,8 @@ import com.sap.sse.datamining.test.util.components.Number; public class TestParallelMultiDimensionalGroupingProcessor { + private final static ProcessorFactory processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor()); + private Processor> processor; private Collection, ?>> receivers; @@ -57,16 +60,16 @@ public class TestParallelMultiDimensionalGroupingProcessor { @Test(expected=IllegalArgumentException.class) public void testConstructionWithEmptyDimensions() { - Iterable> dimensions = new ArrayList<>(); - new ParallelMultiDimensionsValueNestingGroupingProcessor<>(Number.class, ConcurrencyTestsUtil.getExecutor(), receivers, dimensions); + List> dimensions = new ArrayList<>(); + processorFactory.createGroupingProcessor(Number.class, receivers, dimensions); } @Test(expected=IllegalArgumentException.class) public void testConstructionWithFunctionsInsteadOfDimensions() { - Collection> functions = new ArrayList<>(); + List> functions = new ArrayList<>(); Method method = FunctionTestsUtil.getMethodFromSimpleClassWithMarkedMethod("sideEffectFreeValue"); functions.add(FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(method)); - new ParallelMultiDimensionsValueNestingGroupingProcessor<>(Number.class, ConcurrencyTestsUtil.getExecutor(), receivers, functions); + processorFactory.createGroupingProcessor(Number.class, receivers, functions); } @Test diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java index 9d349434d8a..84c002a33b2 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java @@ -8,8 +8,10 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.ProcessorQuery; import com.sap.sse.datamining.impl.components.GroupedDataEntry; @@ -97,7 +99,15 @@ public class ProcessorFactory { } public Processor> createGroupingProcessor(Class dataType, Collection, ?>> resultReceivers, List> dimensionsToGroupBy) { - return new ParallelMultiDimensionsValueNestingGroupingProcessor<>(dataType, executor, resultReceivers, dimensionsToGroupBy); + Collection, ParameterProvider>> dimensionsToGroupByWithNullParameterProvider = new ArrayList<>(); + for (Function dimension : dimensionsToGroupBy) { + dimensionsToGroupByWithNullParameterProvider.add(new Pair, ParameterProvider>(dimension, ParameterProvider.NULL)); + } + return new ParallelMultiDimensionsValueNestingGroupingProcessor<>(dataType, executor, resultReceivers, dimensionsToGroupByWithNullParameterProvider); + } + + public Processor> createGroupingProcessorWithParameter(Class dataType, Collection, ?>> resultReceivers, List, ParameterProvider>> dimensionsToGroupByWithParameterProvider) { + return new ParallelMultiDimensionsValueNestingGroupingProcessor(dataType, executor, resultReceivers, dimensionsToGroupByWithParameterProvider); } /** diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java index 82ba5331753..e08af33291d 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractParallelMultiDimensionalNestingGroupingProcessor.java @@ -5,37 +5,39 @@ import java.util.Iterator; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.AdditionalResultDataBuilder; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.NestingCompoundGroupKey; public abstract class AbstractParallelMultiDimensionalNestingGroupingProcessor extends AbstractSimpleParallelProcessor> { - private Iterable> dimensions; + private Iterable, ParameterProvider>> dimensionsWithParameterProvider; @SuppressWarnings("unchecked") public AbstractParallelMultiDimensionalNestingGroupingProcessor(Class dataType, ExecutorService executor, Collection, ?>> resultReceivers, - Iterable> dimensions) { + Iterable, ParameterProvider>> dimensionsWithParameterProvider) { super(dataType, (Class>)(Class) GroupedDataEntry.class, executor, resultReceivers); - verifyThatDimensionsAreDimensions(dimensions); - this.dimensions = dimensions; + verifyThatDimensionsAreDimensions(dimensionsWithParameterProvider); + this.dimensionsWithParameterProvider = dimensionsWithParameterProvider; } - private void verifyThatDimensionsAreDimensions(Iterable> dimensions) { - if (dimensions == null) { + private void verifyThatDimensionsAreDimensions(Iterable, ParameterProvider>> dimensionsWithParameterProvider) { + if (dimensionsWithParameterProvider == null) { throw new IllegalArgumentException("The given dimensions mustn't be null."); } int size = 0; - for (Function possibleDimension : dimensions) { + for (Pair, ParameterProvider> possibleDimension : dimensionsWithParameterProvider) { size++; - if (!possibleDimension.isDimension()) { - throw new IllegalArgumentException("The given function " + possibleDimension.toString() + " is no dimension."); + if (!possibleDimension.getA().isDimension()) { + throw new IllegalArgumentException("The given function " + possibleDimension.getA().toString() + " is no dimension."); } } @@ -50,21 +52,21 @@ public abstract class AbstractParallelMultiDimensionalNestingGroupingProcessor call() throws Exception { return new GroupedDataEntry(createCompoundKeyFor(element, - dimensions.iterator()), element); + dimensionsWithParameterProvider.iterator()), element); } }; } - private GroupKey createCompoundKeyFor(DataType input, Iterator> dimensionsIterator) { - Function mainDimension = dimensionsIterator.next(); - GroupKey key = createGroupKeyFor(input, mainDimension); + private GroupKey createCompoundKeyFor(DataType input, Iterator, ParameterProvider>> dimensionsIterator) { + Pair, ParameterProvider> mainDimension = dimensionsIterator.next(); + GroupKey key = createGroupKeyFor(input, mainDimension.getA(), mainDimension.getB()); if (dimensionsIterator.hasNext()) { key = new NestingCompoundGroupKey(key, createCompoundKeyFor(input, dimensionsIterator)); } return key; } - protected abstract GroupKey createGroupKeyFor(DataType input, Function dimension); + protected abstract GroupKey createGroupKeyFor(DataType input, Function dimension, ParameterProvider parameterProvider); @Override protected void setAdditionalData(AdditionalResultDataBuilder additionalDataBuilder) { diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelByDimensionGroupingProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelByDimensionGroupingProcessor.java index 5e2221dc0b4..852711f441c 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelByDimensionGroupingProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelByDimensionGroupingProcessor.java @@ -5,9 +5,11 @@ import java.util.Collection; import java.util.Locale; import java.util.concurrent.ExecutorService; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.factories.FunctionDTOFactory; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.dto.FunctionDTO; @@ -38,14 +40,14 @@ public class ParallelByDimensionGroupingProcessor extends functionDTOFactory = new FunctionDTOFactory(); } - private static Iterable> asIterable(Function dimension) { - Collection> collection = new ArrayList<>(); - collection.add(dimension); + private static Iterable, ParameterProvider>> asIterable(Function dimension) { + Collection, ParameterProvider>> collection = new ArrayList<>(); + collection.add(new Pair, ParameterProvider>(dimension, ParameterProvider.NULL)); return collection; } @Override - protected GroupKey createGroupKeyFor(DataType input, Function dimension) { + protected GroupKey createGroupKeyFor(DataType input, Function dimension, ParameterProvider parameterProvider) { return new GenericGroupKey(functionDTOFactory.createFunctionDTO(dimension, stringMessages, locale)); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelGroupedElementsValueExtractionProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelGroupedElementsValueExtractionProcessor.java index 0f2be60bbbf..4bb8f96c202 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelGroupedElementsValueExtractionProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelGroupedElementsValueExtractionProcessor.java @@ -7,18 +7,27 @@ import java.util.concurrent.ExecutorService; import com.sap.sse.datamining.AdditionalResultDataBuilder; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; public class ParallelGroupedElementsValueExtractionProcessor extends AbstractSimpleParallelProcessor, GroupedDataEntry> { private final Function extractionFunction; + private final ParameterProvider parameterProvider; + + public ParallelGroupedElementsValueExtractionProcessor(ExecutorService executor, + Collection, ?>> resultReceivers, + Function extractionFunction) { + this(executor, resultReceivers, extractionFunction, ParameterProvider.NULL); + } @SuppressWarnings("unchecked") public ParallelGroupedElementsValueExtractionProcessor(ExecutorService executor, Collection, ?>> resultReceivers, - Function extractionFunction) { + Function extractionFunction, ParameterProvider parameterProvider) { super((Class>)(Class) GroupedDataEntry.class, (Class>)(Class) GroupedDataEntry.class, executor, resultReceivers); this.extractionFunction = extractionFunction; + this.parameterProvider = parameterProvider; } @Override @@ -26,7 +35,7 @@ public class ParallelGroupedElementsValueExtractionProcessor>() { @Override public GroupedDataEntry call() throws Exception { - FunctionReturnType value = extractionFunction.tryToInvoke(element.getDataEntry()); + FunctionReturnType value = extractionFunction.tryToInvoke(element.getDataEntry(), parameterProvider); return value != null ? new GroupedDataEntry(element.getKey(), value) : ParallelGroupedElementsValueExtractionProcessor.super.createInvalidResult(); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java index a3462632b5d..f2e0ab0e641 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/ParallelMultiDimensionsValueNestingGroupingProcessor.java @@ -3,8 +3,10 @@ package com.sap.sse.datamining.impl.components; import java.util.Collection; import java.util.concurrent.ExecutorService; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.GenericGroupKey; @@ -23,12 +25,12 @@ public class ParallelMultiDimensionsValueNestingGroupingProcessor public ParallelMultiDimensionsValueNestingGroupingProcessor(Class dataType, ExecutorService executor, Collection, ?>> resultReceivers, - Iterable> dimensions) { - super(dataType, executor, resultReceivers, dimensions); + Iterable, ParameterProvider>> dimensionsWithParameterProvider) { + super(dataType, executor, resultReceivers, dimensionsWithParameterProvider); } - protected GroupKey createGroupKeyFor(DataType input, Function dimension) { - Object keyValue = dimension.tryToInvoke(input); + protected GroupKey createGroupKeyFor(DataType input, Function dimension, ParameterProvider parameterProvider) { + Object keyValue = dimension.tryToInvoke(input, parameterProvider); return new GenericGroupKey(keyValue); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java index 12ee202edad..2fb2857896b 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java @@ -5,21 +5,28 @@ import java.util.HashSet; import java.util.Objects; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; public class NullaryFunctionValuesFilterCriterion extends AbstractFilterCriterion { - private Function function; - private Collection valuesToMatch; - + private final Function function; + private final ParameterProvider parameterProvider; + private final Collection valuesToMatch; + public NullaryFunctionValuesFilterCriterion(Class elementType, Function function, Collection valuesToMatch) { + this(elementType, function, ParameterProvider.NULL, valuesToMatch); + } + + public NullaryFunctionValuesFilterCriterion(Class elementType, Function function, ParameterProvider parameterProvider, Collection valuesToMatch) { super(elementType); this.function = function; + this.parameterProvider = parameterProvider; this.valuesToMatch = new HashSet<>(valuesToMatch); } @Override public boolean matches(ElementType dataEntry) { - Object value = function.tryToInvoke(dataEntry); + Object value = function.tryToInvoke(dataEntry, parameterProvider); for (Object valueToMatch : valuesToMatch) { if (Objects.equals(value, valueToMatch)) { From 3c7efb9cdb8b520b85afd28164c04d77be687168 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Tue, 23 Dec 2014 10:14:03 +0100 Subject: [PATCH 005/264] Implemented a simple ParameterProvider workflow --- .../datamining/data/HasGPSFixContext.java | 5 +- .../impl/data/GPSFixWithContext.java | 5 +- .../sailing/polars/mining/PolarDataMiner.java | 8 ++- .../Test_StringMessages_de.properties | 4 +- .../Test_StringMessages_en.properties | 4 +- .../datamining/impl/TestProcessorQuery.java | 5 +- ...llelMultiDimensionalGroupingProcessor.java | 4 +- .../impl/criterias/TestFilterCriteria.java | 2 +- .../impl/functions/TestFunctionProvider.java | 62 +++++++++++++++++-- .../TestMethodWrappingFunctionInvocation.java | 16 ++--- .../SimpleClassWithMarkedMethods.java | 9 +++ .../test/util/ComponentTestsUtil.java | 5 +- .../sap/sse/datamining/DataMiningServer.java | 3 + .../ModifiableDataMiningServer.java | 3 + .../factories/ProcessorFactory.java | 22 +++++-- .../datamining/factories/QueryFactory.java | 28 ++++++--- .../functions/FunctionProvider.java | 2 + .../functions/FunctionRegistry.java | 3 + .../functions/ParameterProvider.java | 3 + .../i18n/DataMiningStringMessages.java | 4 ++ .../datamining/impl/DataMiningServerImpl.java | 30 ++++++++- ...ava => FunctionValuesFilterCriterion.java} | 6 +- .../LocalizationParameterProvider.java | 22 +++++++ .../impl/functions/NullParameterProvider.java | 9 ++- .../functions/RegistryFunctionProvider.java | 39 ++++++++++++ .../functions/SimpleFunctionRegistry.java | 20 ++++++ .../functions/SimpleParameterProvider.java | 50 +++++++++++++++ .../StringMessagesParameterProvider.java | 21 ------- 28 files changed, 323 insertions(+), 71 deletions(-) rename java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/{NullaryFunctionValuesFilterCriterion.java => FunctionValuesFilterCriterion.java} (70%) create mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java create mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java delete mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/StringMessagesParameterProvider.java diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java index d83e690a332..854124e6958 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java @@ -1,6 +1,9 @@ package com.sap.sailing.datamining.data; +import java.util.Locale; + import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.shared.annotations.Connector; import com.sap.sse.datamining.shared.annotations.Dimension; import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; @@ -14,6 +17,6 @@ public interface HasGPSFixContext { public GPSFixMoving getGPSFix(); @Dimension(messageKey="WindStrengthInBeaufort", ordinal=10) - public ClusterDTO getWindStrengthAsBeaufortCluster(); + public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, DataMiningStringMessages stringMessages); } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java index ac5e68d8600..410349ac05d 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java @@ -8,6 +8,7 @@ import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.Wind; import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; public class GPSFixWithContext implements HasGPSFixContext { @@ -33,10 +34,10 @@ public class GPSFixWithContext implements HasGPSFixContext { } @Override - public ClusterDTO getWindStrengthAsBeaufortCluster() { + public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, DataMiningStringMessages stringMessages) { Wind wind = getWind(); Cluster cluster = Activator.getDefault().getClusterGroups().getWindStrengthInBeaufortCluster().getClusterFor(wind); - return new ClusterDTO(cluster.getAsLocalizedString(Locale.ENGLISH, Activator.getDefault().getStringMessages())); + return new ClusterDTO(cluster.getAsLocalizedString(locale, stringMessages)); } private Wind getWind() { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index b5b422e9b6c..67c672476a7 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -32,6 +32,7 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.data.ClusterGroup; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.ParallelFilteringProcessor; import com.sap.sse.datamining.impl.components.ParallelMultiDimensionsValueNestingGroupingProcessor; @@ -72,10 +73,13 @@ public class PolarDataMiner { Collection, ?>> grouperResultReceivers = new ArrayList, ?>>(); grouperResultReceivers.add(incrementalRegressionProcessor); - Collection> dimensions = PolarDataDimensionCollectionFactory.getClusterKeyDimensions(); + Collection, ParameterProvider>> dimensionsWithParameterProviders = new ArrayList<>(); + for (Function function : PolarDataDimensionCollectionFactory.getClusterKeyDimensions()) { + dimensionsWithParameterProviders.add(new Pair, ParameterProvider>(function, ParameterProvider.NULL)); + } Processor> groupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( - GPSFixMovingWithPolarContext.class, executor, grouperResultReceivers, dimensions); + GPSFixMovingWithPolarContext.class, executor, grouperResultReceivers, dimensionsWithParameterProviders); Collection> filteringResultReceivers = Arrays.asList(groupingProcessor); Processor filteringProcessor = new ParallelFilteringProcessor( diff --git a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_de.properties b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_de.properties index 0c9f10b0ac6..3adc508bed7 100644 --- a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_de.properties +++ b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_de.properties @@ -10,4 +10,6 @@ LegNumber=Anzahl der Schenkel SailID=Segelnummer TestCluster=Test Cluster Deutsch ClosedBoundariesClusterGroup=Cluster-Gruppe mit geschlossenen Grenzen -OpenBoundariesClusterGroup=Cluster-Gruppe mit offenen Grenzen \ No newline at end of file +OpenBoundariesClusterGroup=Cluster-Gruppe mit offenen Grenzen +increment=Inkrementieren +illegalDimension=Illegale Dimension \ No newline at end of file diff --git a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties index 9c4ddd0250b..76b690e6bb9 100644 --- a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties +++ b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties @@ -10,4 +10,6 @@ LegNumber=Leg Number SailID=Sail ID TestCluster=Test Cluster English ClosedBoundariesClusterGroup=Cluster group with closed boundaries -OpenBoundariesClusterGroup=Cluster group with open boundaries \ No newline at end of file +OpenBoundariesClusterGroup=Cluster group with open boundaries +increment=Increment +illegalDimension=Illegal Dimension \ No newline at end of file diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java index 96b86b42df0..36e561290dd 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java @@ -26,12 +26,14 @@ import com.sap.sse.datamining.components.FilterCriterion; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.factories.ProcessorFactory; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.FunctionProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.components.AbstractSimpleParallelProcessor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.ParallelFilteringProcessor; import com.sap.sse.datamining.impl.criterias.AbstractFilterCriterion; +import com.sap.sse.datamining.impl.functions.RegistryFunctionProvider; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.QueryResult; import com.sap.sse.datamining.shared.Unit; @@ -57,7 +59,8 @@ public class TestProcessorQuery { @Before public void initializeProcessorFactory() { - processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor()); + FunctionProvider emptyFunctionProvider = new RegistryFunctionProvider(new ArrayList<>()); + processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor(), emptyFunctionProvider); } @Test diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java index 8467f9f3066..5255cbda55f 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java @@ -16,8 +16,8 @@ import com.sap.sse.datamining.factories.ProcessorFactory; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.impl.functions.MethodWrappingFunction; import com.sap.sse.datamining.shared.GroupKey; -import com.sap.sse.datamining.shared.impl.NestingCompoundGroupKey; import com.sap.sse.datamining.shared.impl.GenericGroupKey; +import com.sap.sse.datamining.shared.impl.NestingCompoundGroupKey; import com.sap.sse.datamining.test.util.ComponentTestsUtil; import com.sap.sse.datamining.test.util.ConcurrencyTestsUtil; import com.sap.sse.datamining.test.util.FunctionTestsUtil; @@ -26,7 +26,7 @@ import com.sap.sse.datamining.test.util.components.Number; public class TestParallelMultiDimensionalGroupingProcessor { - private final static ProcessorFactory processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor()); + private final static ProcessorFactory processorFactory = ComponentTestsUtil.getProcessorFactory(); private Processor> processor; private Collection, ?>> receivers; diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/criterias/TestFilterCriteria.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/criterias/TestFilterCriteria.java index a1adfcf500d..a5bbcf77bc7 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/criterias/TestFilterCriteria.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/criterias/TestFilterCriteria.java @@ -73,7 +73,7 @@ public class TestFilterCriteria { public void testNullaryFunctionValuesFilterCriterion() { Function getRegattaName = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(FunctionTestsUtil.getMethodFromClass(DataTypeWithContext.class, "getRegattaName")); Collection valuesToMatch = Arrays.asList("Regatta", "Other Regatta"); - FilterCriterion nullaryFunctionFilterCriterion = new NullaryFunctionValuesFilterCriterion<>(DataTypeWithContext.class, getRegattaName, valuesToMatch); + FilterCriterion nullaryFunctionFilterCriterion = new FunctionValuesFilterCriterion<>(DataTypeWithContext.class, getRegattaName, valuesToMatch); DataTypeWithContext regatta = new DataTypeWithContextImpl("Regatta", "Race Name", 7); assertThat(nullaryFunctionFilterCriterion.matches(regatta), is(true)); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java index 3a63c400e67..74a0ca3f2fe 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java @@ -5,6 +5,7 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -18,6 +19,7 @@ import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionProvider; import com.sap.sse.datamining.functions.FunctionRegistry; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.SimpleDataRetrieverChainDefinition; import com.sap.sse.datamining.shared.dto.FunctionDTO; @@ -146,12 +148,12 @@ public class TestFunctionProvider { @Test public void testGetFunctionForUnregisteredDTO() { - Method incrementMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "increment", int.class); - Function increment = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(incrementMethod); - FunctionDTO incrementDTO = FunctionTestsUtil.getFunctionDTOFactory().createFunctionDTO(increment, stringMessages, Locale.ENGLISH); + Method illegalDimensionMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "illegalDimension"); + Function illegalDimension = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(illegalDimensionMethod); + FunctionDTO illegalDimensionDTO = FunctionTestsUtil.getFunctionDTOFactory().createFunctionDTO(illegalDimension, stringMessages, Locale.ENGLISH); FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); - assertThat(functionProvider.getFunctionForDTO(incrementDTO), is(nullValue())); + assertThat(functionProvider.getFunctionForDTO(illegalDimensionDTO), is(nullValue())); } @Test @@ -159,5 +161,57 @@ public class TestFunctionProvider { FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); assertThat(functionProvider.getFunctionForDTO(null), is(nullValue())); } + + @Test + public void testGetParameterProviderForNullaryFunction() { + Method nullaryDimensionMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "dimension"); + Function nullaryDimension = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(nullaryDimensionMethod); + + FunctionRegistry functionRegistry = new SimpleFunctionRegistry(); + FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); + assertThat(functionProvider.getParameterProviderFor(nullaryDimension), is(ParameterProvider.NULL)); + } + + @Test + public void testGetPrameterProviderForFunctionsWithParameters() { + FunctionRegistry functionRegistry = new SimpleFunctionRegistry(); + ParameterProvider intParameterProvider = new ParameterProvider() { + @Override + public Object[] getParameters() { + return new Object[] {1}; + } + + @Override + public Iterable> getParameterTypes() { + Collection> parameterTypes = new ArrayList<>(); + parameterTypes.add(int.class); + return parameterTypes; + } + }; + functionRegistry.registerParameterProvider(intParameterProvider); + LocalizationParameterProvider stringMessagesParameterProvider = new LocalizationParameterProvider(Locale.ENGLISH, stringMessages); + functionRegistry.registerParameterProvider(stringMessagesParameterProvider); + FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); + + Method incrementMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "increment", int.class); + Function increment = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(incrementMethod); + assertThat(functionProvider.getParameterProviderFor(increment), is(intParameterProvider)); + + Method getLocalizedNameMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "getLocalizedName", Locale.class, DataMiningStringMessages.class); + Function getLocalizedName = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(getLocalizedNameMethod); + assertThat(functionProvider.getParameterProviderFor(getLocalizedName), is(stringMessagesParameterProvider)); + } + + @Test + public void testGetParameterProviderForFunctionWithoutAnAvailableParameterProvider() { + FunctionRegistry functionRegistry = new SimpleFunctionRegistry(); + //Adding a wrong ParameterProvider to increase code coverage + functionRegistry.registerParameterProvider(new LocalizationParameterProvider(Locale.ENGLISH, stringMessages)); + FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); + + Method incrementMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "increment", int.class); + Function increment = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(incrementMethod); + assertThat(functionProvider.getParameterProviderFor(increment), nullValue()); + } } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java index b6cdbe52b4d..6420c1f13ce 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java @@ -4,6 +4,8 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; +import java.util.Collections; + import org.junit.Before; import org.junit.Test; @@ -35,24 +37,14 @@ public class TestMethodWrappingFunctionInvocation { public void testInvocationWithParameters() { SimpleClassWithMarkedMethods instance = new SimpleClassWithMarkedMethods(); final int valueToIncrement = 10; - ParameterProvider parameterProvider = new ParameterProvider() { - @Override - public Object[] getParameters() { - return new Object[] {valueToIncrement}; - } - }; + ParameterProvider parameterProvider = new SimpleParameterProvider(Collections.emptyList(), new Object[] {valueToIncrement}); assertThat(increment.tryToInvoke(instance, parameterProvider), is(instance.increment(valueToIncrement))); } @Test public void testInvocationWithWrongParameters() { DataTypeWithContext dataEntry = new DataTypeWithContextImpl("Regatta Name", "Race Name", 7); - ParameterProvider parameterProvider = new ParameterProvider() { - @Override - public Object[] getParameters() { - return new Object[] {"Wrong Parameter"}; - } - }; + ParameterProvider parameterProvider = new SimpleParameterProvider(Collections.emptyList(), new Object[] {"Wrong Parameter"}); assertThat(getRegattaName.tryToInvoke(dataEntry, parameterProvider), is(nullValue())); SimpleClassWithMarkedMethods instance = new SimpleClassWithMarkedMethods(); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/functions/test_classes/SimpleClassWithMarkedMethods.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/functions/test_classes/SimpleClassWithMarkedMethods.java index 35cc17cec34..99aed372082 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/functions/test_classes/SimpleClassWithMarkedMethods.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/functions/test_classes/SimpleClassWithMarkedMethods.java @@ -1,5 +1,8 @@ package com.sap.sse.datamining.test.functions.test_classes; +import java.util.Locale; + +import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.shared.annotations.Dimension; import com.sap.sse.datamining.shared.annotations.Statistic; @@ -30,8 +33,14 @@ public class SimpleClassWithMarkedMethods { } + //Methods with parameters + public int increment(int i) { return i + 1; } + + public String getLocalizedName(Locale locale, DataMiningStringMessages stringMessages) { + return ""; + } } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java index fbc7167a21f..83e2863422f 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java @@ -5,6 +5,8 @@ import java.util.Arrays; import java.util.Collection; import com.sap.sse.datamining.factories.ProcessorFactory; +import com.sap.sse.datamining.functions.FunctionProvider; +import com.sap.sse.datamining.impl.functions.RegistryFunctionProvider; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_Boat; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_BoatClass; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_Competitor; @@ -22,7 +24,8 @@ import com.sap.sse.datamining.test.functions.registry.test_classes.impl.Test_Tea public final class ComponentTestsUtil { - private final static ProcessorFactory processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor()); + private final static FunctionProvider emptyFunctionProvider = new RegistryFunctionProvider(new ArrayList<>()); + private final static ProcessorFactory processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor(), emptyFunctionProvider); public static ProcessorFactory getProcessorFactory() { return processorFactory; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataMiningServer.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataMiningServer.java index e8a3102a089..76fe4583f0a 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataMiningServer.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataMiningServer.java @@ -7,6 +7,7 @@ import java.util.concurrent.ExecutorService; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionProvider; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.DataRetrieverChainDefinitionProvider; import com.sap.sse.datamining.shared.QueryDefinitionDTO; @@ -34,6 +35,8 @@ public interface DataMiningServer { * @return The first function, that matches the given DTO or null */ public Function getFunctionForDTO(FunctionDTO functionDTO); + + public ParameterProvider getParameterProviderFor(Function function); public DataRetrieverChainDefinitionProvider getDataRetrieverChainDefinitionProvider(); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/ModifiableDataMiningServer.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/ModifiableDataMiningServer.java index a2448222b25..d8e8f6cc250 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/ModifiableDataMiningServer.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/ModifiableDataMiningServer.java @@ -1,6 +1,7 @@ package com.sap.sse.datamining; import com.sap.sse.datamining.functions.FunctionRegistry; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; public interface ModifiableDataMiningServer extends DataMiningServer { @@ -12,8 +13,10 @@ public interface ModifiableDataMiningServer extends DataMiningServer { public void registerAllWithInternalFunctionPolicy(Iterable> classesToScan); public void registerAllWithExternalFunctionPolicy(Iterable> externalClassesToScan); + public void registerParameterProvider(ParameterProvider parameterProvider); public void unregisterAllFunctionsOf(Iterable> classesToUnregister); + public void unregisterParameterProvider(ParameterProvider parameterProvider); public void registerDataRetrieverChainDefinition(DataRetrieverChainDefinition dataRetrieverChainDefinition); public void unregisterDataRetrieverChainDefinition(DataRetrieverChainDefinition dataRetrieverChainDefinition); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java index 84c002a33b2..edbe5ce44f1 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java @@ -11,6 +11,7 @@ import java.util.concurrent.ExecutorService; import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.FunctionProvider; import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.ProcessorQuery; @@ -28,9 +29,11 @@ import com.sap.sse.datamining.shared.components.AggregatorType; public class ProcessorFactory { private final ExecutorService executor; + private final FunctionProvider functionProvider; - public ProcessorFactory(ExecutorService executor) { + public ProcessorFactory(ExecutorService executor, FunctionProvider functionProvider) { this.executor = executor; + this.functionProvider = functionProvider; } /** @@ -85,8 +88,10 @@ public class ProcessorFactory { public Processor, GroupedDataEntry> createExtractionProcessor( Collection, ?>> resultReceivers, Function extractionFunction) { + ParameterProvider parameterProvider = functionProvider.getParameterProviderFor(extractionFunction); + throwExceptionIfParameterProviderIsNull(extractionFunction, parameterProvider); return new ParallelGroupedElementsValueExtractionProcessor(executor, - resultReceivers, extractionFunction); + resultReceivers, extractionFunction, parameterProvider); } /** @@ -101,10 +106,19 @@ public class ProcessorFactory { public Processor> createGroupingProcessor(Class dataType, Collection, ?>> resultReceivers, List> dimensionsToGroupBy) { Collection, ParameterProvider>> dimensionsToGroupByWithNullParameterProvider = new ArrayList<>(); for (Function dimension : dimensionsToGroupBy) { - dimensionsToGroupByWithNullParameterProvider.add(new Pair, ParameterProvider>(dimension, ParameterProvider.NULL)); + ParameterProvider parameterProvider = functionProvider.getParameterProviderFor(dimension); + throwExceptionIfParameterProviderIsNull(dimension, parameterProvider); + dimensionsToGroupByWithNullParameterProvider.add(new Pair, ParameterProvider>(dimension, parameterProvider)); } return new ParallelMultiDimensionsValueNestingGroupingProcessor<>(dataType, executor, resultReceivers, dimensionsToGroupByWithNullParameterProvider); } + + private void throwExceptionIfParameterProviderIsNull(Function function, ParameterProvider parameterProvider) { + if (parameterProvider == null) { + throw new NullPointerException("There is no " + ParameterProvider.class.getSimpleName() + " available for the " + + Function.class.getSimpleName() + " '" + function.toString() + "'"); + } + } public Processor> createGroupingProcessorWithParameter(Class dataType, Collection, ?>> resultReceivers, List, ParameterProvider>> dimensionsToGroupByWithParameterProvider) { return new ParallelMultiDimensionsValueNestingGroupingProcessor(dataType, executor, resultReceivers, dimensionsToGroupByWithParameterProvider); @@ -122,7 +136,7 @@ public class ProcessorFactory { Collection>> groupingExtractors = new ArrayList<>(); for (Function dimension : dimensions) { - Processor, GroupedDataEntry> dimensionValueExtractor = new ParallelGroupedElementsValueExtractionProcessor(executor, extractionResultReceivers, (Function) dimension); + Processor, GroupedDataEntry> dimensionValueExtractor = createExtractionProcessor(extractionResultReceivers, (Function) dimension); Collection, ?>> groupingResultReceivers = new ArrayList<>(); groupingResultReceivers.add(dimensionValueExtractor); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java index 0554759f9c2..8e17ab947fd 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java @@ -16,22 +16,24 @@ import com.sap.sse.datamining.QueryDefinition; import com.sap.sse.datamining.components.FilterCriterion; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.FunctionProvider; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.ProcessorQuery; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.criterias.AndCompoundFilterCriterion; import com.sap.sse.datamining.impl.criterias.CompoundFilterCriterion; -import com.sap.sse.datamining.impl.criterias.NullaryFunctionValuesFilterCriterion; +import com.sap.sse.datamining.impl.criterias.FunctionValuesFilterCriterion; import com.sap.sse.datamining.shared.GroupKey; public class QueryFactory { public Query createQuery(DataSourceType dataSource, QueryDefinition queryDefinition, - DataMiningStringMessages stringMessages, ExecutorService executor) { + DataMiningStringMessages stringMessages, ExecutorService executor, FunctionProvider functionProvider) { return new ProcessorQuery(dataSource, stringMessages, queryDefinition.getLocale()) { @Override protected Processor createFirstProcessor() { - ProcessorFactory processorFactory = new ProcessorFactory(executor); + ProcessorFactory processorFactory = new ProcessorFactory(executor, functionProvider); Function extractionFunction = queryDefinition.getStatisticToCalculate(); Class dataTypeToRetrieve = queryDefinition.getDataType(); @@ -42,7 +44,7 @@ public class QueryFactory { Processor> groupingProcessor = processorFactory.createGroupingProcessor(dataTypeToRetrieve, extractionProcessor, queryDefinition.getDimensionsToGroupBy()); DataRetrieverChainBuilder chainBuilder = queryDefinition.getDataRetrieverChainDefinition().startBuilding(executor); - Map> criteriaMappedByRetrieverLevel = createFilterCriteria(queryDefinition.getFilterSelection()); + Map> criteriaMappedByRetrieverLevel = createFilterCriteria(queryDefinition.getFilterSelection(), functionProvider); while (chainBuilder.canStepFurther()) { chainBuilder.stepFurther(); @@ -59,11 +61,13 @@ public class QueryFactory { } @SuppressWarnings("unchecked") - private Map> createFilterCriteria(Map, Collection>> filterSelection) { + private Map> createFilterCriteria(Map, Collection>> filterSelection, FunctionProvider functionProvider) { Map> criteriaMappedByRetrieverLevel = new HashMap<>(); for (Entry, Collection>> levelFilterSelection : filterSelection.entrySet()) { for (Entry, Collection> levelFilterSelectionEntry : levelFilterSelection.getValue().entrySet()) { Function function = levelFilterSelectionEntry.getKey(); + ParameterProvider parameterProvider = functionProvider.getParameterProviderFor(function); + throwExceptionIfParameterProviderIsNull(function, parameterProvider); Class dataType = (Class) function.getDeclaringType(); if (!criteriaMappedByRetrieverLevel.containsKey(levelFilterSelection.getKey())) { @@ -71,20 +75,28 @@ public class QueryFactory { } Collection filterValues = new ArrayList<>(levelFilterSelectionEntry.getValue()); - ((CompoundFilterCriterion) criteriaMappedByRetrieverLevel.get(levelFilterSelection.getKey())).addCriteria(new NullaryFunctionValuesFilterCriterion<>(dataType, function, filterValues)); + ((CompoundFilterCriterion) criteriaMappedByRetrieverLevel.get(levelFilterSelection.getKey())).addCriteria(new FunctionValuesFilterCriterion<>(dataType, function, parameterProvider, filterValues)); } } return (Map>)(Map) criteriaMappedByRetrieverLevel; } + + private void throwExceptionIfParameterProviderIsNull(Function function, ParameterProvider parameterProvider) { + if (parameterProvider == null) { + throw new NullPointerException("There is no " + ParameterProvider.class.getSimpleName() + " available for the " + + Function.class.getSimpleName() + " '" + function.toString() + "'"); + } + } public Query> createDimensionValuesQuery(DataSource dataSource, DataRetrieverChainDefinition dataRetrieverChainDefinition, int retrieverLevel, Iterable> dimensions, Locale locale, - DataMiningStringMessages stringMessages, ExecutorService executor) { + DataMiningStringMessages stringMessages, ExecutorService executor, + FunctionProvider functionProvider) { return new ProcessorQuery, DataSource>(dataSource, stringMessages, locale) { @Override protected Processor createFirstProcessor() { - ProcessorFactory processorFactory = new ProcessorFactory(executor); + ProcessorFactory processorFactory = new ProcessorFactory(executor, functionProvider); Processor, Map>> valueCollector = processorFactory.createGroupedDataCollectingAsSetProcessor(/*query*/ this); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java index 6a8c3f450b9..e40e3176a04 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java @@ -21,5 +21,7 @@ public interface FunctionProvider { * @return The first function, that matches the given DTO or null */ public Function getFunctionForDTO(FunctionDTO functionDTO); + + public ParameterProvider getParameterProviderFor(Function function); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java index 4f0a06a33ec..ddb4751863b 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java @@ -6,8 +6,10 @@ public interface FunctionRegistry { public void registerAllWithInternalFunctionPolicy(Iterable> classesToScan); public void registerAllWithExternalFunctionPolicy(Iterable> externalClassesToScan); + public void registerParameterProvider(ParameterProvider parameterProvider); public void unregisterAllFunctionsOf(Iterable> classesToUnregister); + public void unregisterParameterProvider(ParameterProvider parameterProvider); public Collection> getAllFunctions(); public Collection> getAllFunctionsOf(Class declaringType); @@ -17,5 +19,6 @@ public interface FunctionRegistry { public Collection> getDimensionsOf(Class declaringType); public Collection> getExternalFunctions(); public Collection> getExternalFunctionsOf(Class declaringType); + public Iterable getAllParameterProviders(); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java index 32d37a9487b..d17f511e5d6 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java @@ -6,6 +6,9 @@ public interface ParameterProvider { public static final ParameterProvider NULL = new NullParameterProvider(); + public Iterable> getParameterTypes(); + public Object[] getParameters(); + } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/i18n/DataMiningStringMessages.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/i18n/DataMiningStringMessages.java index 64a1efcfa42..0da3a28e2d9 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/i18n/DataMiningStringMessages.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/i18n/DataMiningStringMessages.java @@ -27,6 +27,10 @@ public interface DataMiningStringMessages { return locale != null ? locale : DEFAULT_LOCALE; } + public static Iterable getSupportedLocales() { + return supportedLocalesMappedByLocaleInfo.values(); + } + private static void initializeSupportedLocales() { supportedLocalesMappedByLocaleInfo.put(DEFAULT_LOCALE_NAME, DEFAULT_LOCALE); supportedLocalesMappedByLocaleInfo.put("en", Locale.ENGLISH); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java index afd7e961386..8e7d6cda6db 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java @@ -17,7 +17,9 @@ import com.sap.sse.datamining.factories.QueryFactory; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionProvider; import com.sap.sse.datamining.functions.FunctionRegistry; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; +import com.sap.sse.datamining.impl.functions.LocalizationParameterProvider; import com.sap.sse.datamining.impl.i18n.CompoundDataMiningStringMessages; import com.sap.sse.datamining.shared.QueryDefinitionDTO; import com.sap.sse.datamining.shared.dto.FunctionDTO; @@ -41,8 +43,17 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { this.functionRegistry = functionRegistry; this.functionProvider = functionProvider; this.dataRetrieverChainDefinitionRegistry = dataRetrieverChainDefinitionRegistry; + + initializeStringMessagesParameterProvider(); } + private void initializeStringMessagesParameterProvider() { + for (Locale locale : DataMiningStringMessages.Util.getSupportedLocales()) { + ParameterProvider stringMessagesParameterProvider = new LocalizationParameterProvider(locale, getStringMessages()); + registerParameterProvider(stringMessagesParameterProvider); + } + } + @Override public ExecutorService getExecutorService() { return executorService; @@ -78,10 +89,20 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { functionRegistry.registerAllWithExternalFunctionPolicy(externalClassesToScan); } + @Override + public void registerParameterProvider(ParameterProvider parameterProvider) { + functionRegistry.registerParameterProvider(parameterProvider); + } + @Override public void unregisterAllFunctionsOf(Iterable> classesToUnregister) { functionRegistry.unregisterAllFunctionsOf(classesToUnregister); } + + @Override + public void unregisterParameterProvider(ParameterProvider parameterProvider) { + functionRegistry.unregisterParameterProvider(parameterProvider); + } @Override public FunctionProvider getFunctionProvider() { @@ -118,6 +139,11 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { return functionProvider.getFunctionForDTO(functionDTO); } + @Override + public ParameterProvider getParameterProviderFor(Function function) { + return functionProvider.getParameterProviderFor(function); + } + @Override public DataRetrieverChainDefinitionProvider getDataRetrieverChainDefinitionProvider() { return dataRetrieverChainDefinitionRegistry; @@ -185,14 +211,14 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { @Override public Query createQuery(DataSource dataSource, QueryDefinition queryDefinition) { - return queryFactory.createQuery(dataSource, queryDefinition, getStringMessages(), getExecutorService()); + return queryFactory.createQuery(dataSource, queryDefinition, getStringMessages(), getExecutorService(), getFunctionProvider()); } @Override public Query> createDimensionValuesQuery(DataSource dataSource, DataRetrieverChainDefinition dataRetrieverChainDefinition, int retrieverLevel, Iterable> dimensions, Locale locale) { - return queryFactory.createDimensionValuesQuery(dataSource, dataRetrieverChainDefinition, retrieverLevel, dimensions, locale, getStringMessages(), getExecutorService()); + return queryFactory.createDimensionValuesQuery(dataSource, dataRetrieverChainDefinition, retrieverLevel, dimensions, locale, getStringMessages(), getExecutorService(), getFunctionProvider()); } } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/FunctionValuesFilterCriterion.java similarity index 70% rename from java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java rename to java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/FunctionValuesFilterCriterion.java index 2fb2857896b..7e4531b350f 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/NullaryFunctionValuesFilterCriterion.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/criterias/FunctionValuesFilterCriterion.java @@ -7,17 +7,17 @@ import java.util.Objects; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.ParameterProvider; -public class NullaryFunctionValuesFilterCriterion extends AbstractFilterCriterion { +public class FunctionValuesFilterCriterion extends AbstractFilterCriterion { private final Function function; private final ParameterProvider parameterProvider; private final Collection valuesToMatch; - public NullaryFunctionValuesFilterCriterion(Class elementType, Function function, Collection valuesToMatch) { + public FunctionValuesFilterCriterion(Class elementType, Function function, Collection valuesToMatch) { this(elementType, function, ParameterProvider.NULL, valuesToMatch); } - public NullaryFunctionValuesFilterCriterion(Class elementType, Function function, ParameterProvider parameterProvider, Collection valuesToMatch) { + public FunctionValuesFilterCriterion(Class elementType, Function function, ParameterProvider parameterProvider, Collection valuesToMatch) { super(elementType); this.function = function; this.parameterProvider = parameterProvider; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java new file mode 100644 index 00000000000..846f1e2d030 --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java @@ -0,0 +1,22 @@ +package com.sap.sse.datamining.impl.functions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Locale; + +import com.sap.sse.datamining.i18n.DataMiningStringMessages; + +public class LocalizationParameterProvider extends SimpleParameterProvider { + + public LocalizationParameterProvider(Locale locale, DataMiningStringMessages stringMessages) { + super(createParameterTypes(), new Object[] {locale, stringMessages}); + } + + private static Iterable> createParameterTypes() { + Collection> parameterTypes = new ArrayList<>(); + parameterTypes.add(Locale.class); + parameterTypes.add(DataMiningStringMessages.class); + return parameterTypes; + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/NullParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/NullParameterProvider.java index 03775279207..4dcb9912292 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/NullParameterProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/NullParameterProvider.java @@ -1,12 +1,11 @@ package com.sap.sse.datamining.impl.functions; -import com.sap.sse.datamining.functions.ParameterProvider; +import java.util.Collections; -public class NullParameterProvider implements ParameterProvider { +public class NullParameterProvider extends SimpleParameterProvider { - @Override - public Object[] getParameters() { - return new Object[0]; + public NullParameterProvider() { + super(Collections.emptyList(), new Object[0]); } } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/RegistryFunctionProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/RegistryFunctionProvider.java index e31ba5e047e..a4dd669c8a6 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/RegistryFunctionProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/RegistryFunctionProvider.java @@ -3,6 +3,7 @@ package com.sap.sse.datamining.impl.functions; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; @@ -12,6 +13,7 @@ import com.sap.sse.datamining.factories.FunctionDTOFactory; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionProvider; import com.sap.sse.datamining.functions.FunctionRegistry; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.DataRetrieverTypeWithInformation; import com.sap.sse.datamining.impl.functions.criterias.FunctionMatchesDTOFilterCriterion; import com.sap.sse.datamining.shared.dto.FunctionDTO; @@ -187,5 +189,42 @@ public class RegistryFunctionProvider implements FunctionProvider { private Function getFunctionToReturn(Collection> functionsMatchingDTO) { return functionsMatchingDTO.isEmpty() ? null : functionsMatchingDTO.iterator().next(); } + + @Override + public ParameterProvider getParameterProviderFor(Function function) { + Iterable> functionParameterTypes = function.getParameters(); + if (!functionParameterTypes.iterator().hasNext()) { + return ParameterProvider.NULL; + } + + for (FunctionRegistry functionRegistry : functionRegistries) { + for (ParameterProvider parameterProvider : functionRegistry.getAllParameterProviders()) { + if (parametersAreMatching(function, parameterProvider)) { + return parameterProvider; + } + } + } + + return null; + } + + private boolean parametersAreMatching(Function function, ParameterProvider parameterProvider) { + Iterator> functionParametersIterator = function.getParameters().iterator(); + Iterator> parameterProviderIterator = parameterProvider.getParameterTypes().iterator(); + + while (functionParametersIterator.hasNext() && parameterProviderIterator.hasNext()) { + Class functionParameter = functionParametersIterator.next(); + Class parameterProviderParameter = parameterProviderIterator.next(); + if (!functionParameter.isAssignableFrom(parameterProviderParameter)) { + return false; + } + } + + if (functionParametersIterator.hasNext() || parameterProviderIterator.hasNext()) { + return false; + } + + return true; + } } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java index 34652465a9d..3d8d4e70b7d 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java @@ -14,6 +14,7 @@ import com.sap.sse.datamining.components.FilterCriterion; import com.sap.sse.datamining.factories.FunctionFactory; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionRegistry; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidConnectorFilterCriterion; import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidDimensionFilterCriterion; import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidExternalFunctionFilterCriterion; @@ -33,6 +34,8 @@ public class SimpleFunctionRegistry implements FunctionRegistry { private final Map, Set>> externalFunctions; private final Collection, Set>>> functionMaps; + + private final Collection parameterProviders; public SimpleFunctionRegistry() { functionFactory = new FunctionFactory(); @@ -45,6 +48,8 @@ public class SimpleFunctionRegistry implements FunctionRegistry { functionMaps.add(statistics); functionMaps.add(dimensions); functionMaps.add(externalFunctions); + + parameterProviders = new HashSet<>(); } @Override @@ -127,6 +132,11 @@ public class SimpleFunctionRegistry implements FunctionRegistry { } externalFunctions.get(declaringType).add(function); } + + @Override + public void registerParameterProvider(ParameterProvider parameterProvider) { + parameterProviders.add(parameterProvider); + } @Override public void unregisterAllFunctionsOf(Iterable> classesToUnregister) { @@ -140,6 +150,11 @@ public class SimpleFunctionRegistry implements FunctionRegistry { functionMap.remove(classToUnregister); } } + + @Override + public void unregisterParameterProvider(ParameterProvider parameterProvider) { + parameterProviders.remove(parameterProvider); + } @Override public Collection> getAllFunctions() { @@ -199,5 +214,10 @@ public class SimpleFunctionRegistry implements FunctionRegistry { } return set; } + + @Override + public Iterable getAllParameterProviders() { + return parameterProviders; + } } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java new file mode 100644 index 00000000000..c3b83f0e1a0 --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java @@ -0,0 +1,50 @@ +package com.sap.sse.datamining.impl.functions; + +import com.sap.sse.datamining.functions.ParameterProvider; + +public class SimpleParameterProvider implements ParameterProvider { + + private final Iterable> parameterTypes; + private final Object[] parameters; + + public SimpleParameterProvider(Iterable> parameterTypes, Object[] parameters) { + this.parameterTypes = parameterTypes; + this.parameters = parameters; + } + + @Override + public Iterable> getParameterTypes() { + return parameterTypes; + } + + @Override + public Object[] getParameters() { + return parameters; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((parameterTypes == null) ? 0 : parameterTypes.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SimpleParameterProvider other = (SimpleParameterProvider) obj; + if (parameterTypes == null) { + if (other.parameterTypes != null) + return false; + } else if (!parameterTypes.equals(other.parameterTypes)) + return false; + return true; + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/StringMessagesParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/StringMessagesParameterProvider.java deleted file mode 100644 index 2ba3b04d21f..00000000000 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/StringMessagesParameterProvider.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.sap.sse.datamining.impl.functions; - -import java.util.Locale; - -import com.sap.sse.datamining.functions.ParameterProvider; -import com.sap.sse.datamining.i18n.DataMiningStringMessages; - -public class StringMessagesParameterProvider implements ParameterProvider { - - private final Object[] parameters; - - public StringMessagesParameterProvider(Locale locale, DataMiningStringMessages stringMessages) { - parameters = new Object[] {locale, stringMessages}; - } - - @Override - public Object[] getParameters() { - return parameters; - } - -} From 171fff89be1d1da7395dd26906ccc1a32f71a69f Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Wed, 24 Dec 2014 14:34:58 +0100 Subject: [PATCH 006/264] Unified the implementation of FunctionRegistry and FunctionProvider --- .../TestSailingFunctionsRegistration.java | 4 +- .../datamining/impl/TestProcessorQuery.java | 4 +- ...estFunctionManagerAsFunctionProvider.java} | 67 ++- ...estFunctionManagerAsFunctionRegistry.java} | 6 +- .../test/util/ComponentTestsUtil.java | 4 +- .../datamining/impl/DataMiningActivator.java | 10 +- .../impl/functions/FunctionManager.java | 420 ++++++++++++++++++ .../functions/RegistryFunctionProvider.java | 230 ---------- .../functions/SimpleFunctionRegistry.java | 223 ---------- 9 files changed, 459 insertions(+), 509 deletions(-) rename java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/{TestFunctionProvider.java => TestFunctionManagerAsFunctionProvider.java} (73%) rename java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/{TestSimpleFunctionRegistry.java => TestFunctionManagerAsFunctionRegistry.java} (96%) create mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java delete mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/RegistryFunctionProvider.java delete mode 100644 java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java index ec2d199933b..cd8a44cbace 100644 --- a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java @@ -24,7 +24,7 @@ import com.sap.sse.common.Named; import com.sap.sse.datamining.factories.FunctionFactory; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.impl.functions.SimpleFunctionRegistry; +import com.sap.sse.datamining.impl.functions.FunctionManager; public class TestSailingFunctionsRegistration { @@ -34,7 +34,7 @@ public class TestSailingFunctionsRegistration { @Before public void setUpFunctionRegistryAndProvider() { - functionRegistry = new SimpleFunctionRegistry(); + functionRegistry = new FunctionManager(); functionRegistry.registerAllWithInternalFunctionPolicy(Activator.getDefault().getInternalClassesWithMarkedMethods()); } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java index 36e561290dd..1fe21de993d 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java @@ -33,7 +33,7 @@ import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.ParallelFilteringProcessor; import com.sap.sse.datamining.impl.criterias.AbstractFilterCriterion; -import com.sap.sse.datamining.impl.functions.RegistryFunctionProvider; +import com.sap.sse.datamining.impl.functions.FunctionManager; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.QueryResult; import com.sap.sse.datamining.shared.Unit; @@ -59,7 +59,7 @@ public class TestProcessorQuery { @Before public void initializeProcessorFactory() { - FunctionProvider emptyFunctionProvider = new RegistryFunctionProvider(new ArrayList<>()); + FunctionProvider emptyFunctionProvider = new FunctionManager(); processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor(), emptyFunctionProvider); } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java similarity index 73% rename from java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java rename to java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java index 74a0ca3f2fe..3dfa5f63a32 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionProvider.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java @@ -18,7 +18,6 @@ import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionProvider; -import com.sap.sse.datamining.functions.FunctionRegistry; import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.SimpleDataRetrieverChainDefinition; @@ -38,56 +37,53 @@ import com.sap.sse.datamining.test.util.components.TestLegOfCompetitorWithContex import com.sap.sse.datamining.test.util.components.TestRaceWithContextRetrievalProcessor; import com.sap.sse.datamining.test.util.components.TestRegattaRetrievalProcessor; -public class TestFunctionProvider { +public class TestFunctionManagerAsFunctionProvider { private static final DataMiningStringMessages stringMessages = TestsUtil.getTestStringMessagesWithProductiveMessages(); private ExpectedFunctionRegistryUtil functionRegistryUtil; - private FunctionRegistry functionRegistry; + private FunctionManager functionManager; @Before public void initializeFunctionRegistry() throws NoSuchMethodException, SecurityException { functionRegistryUtil = new ExpectedFunctionRegistryUtil(); - functionRegistry = new SimpleFunctionRegistry(); + functionManager = new FunctionManager(); Collection> internalClassesToScan = new HashSet<>(); internalClassesToScan.add(Test_HasLegOfCompetitorContext.class); internalClassesToScan.add(Test_HasRaceContext.class); - functionRegistry.registerAllWithInternalFunctionPolicy(internalClassesToScan); + functionManager.registerAllWithInternalFunctionPolicy(internalClassesToScan); Collection> externalClassesToScan = new HashSet<>(); externalClassesToScan.add(Test_ExternalLibraryClass.class); - functionRegistry.registerAllWithExternalFunctionPolicy(externalClassesToScan); + functionManager.registerAllWithExternalFunctionPolicy(externalClassesToScan); } @Test public void testGetAllStatistics() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); Collection> expectedFunctions = functionRegistryUtil.getAllExpectedStatistics(); - assertThat(functionProvider.getAllStatistics(), is(expectedFunctions)); + assertThat(functionManager.getAllStatistics(), is(expectedFunctions)); } @Test public void testGetDimensionsForType() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); Collection> expectedDimensions = functionRegistryUtil.getExpectedDimensionsFor(Test_HasRaceContext.class); - assertThat(functionProvider.getDimensionsFor(Test_HasRaceContext.class), is(expectedDimensions)); - assertThat(functionProvider.getDimensionsFor(Test_HasRaceContextImpl.class), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(Test_HasRaceContext.class), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(Test_HasRaceContextImpl.class), is(expectedDimensions)); expectedDimensions.addAll(functionRegistryUtil.getExpectedDimensionsFor(Test_HasLegOfCompetitorContext.class)); - assertThat(functionProvider.getDimensionsFor(Test_HasLegOfCompetitorContext.class), is(expectedDimensions)); - assertThat(functionProvider.getDimensionsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(Test_HasLegOfCompetitorContext.class), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedDimensions)); } @Test public void testGetDimensionsForDataRetrieverChainDefinition() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); DataRetrieverChainDefinition, Test_HasLegOfCompetitorContext> dataRetrieverChainDefinition = createDataRetrieverChainDefinition(); Collection> expectedDimensions = functionRegistryUtil.getExpectedDimensionsFor(Test_HasRaceContext.class); expectedDimensions.addAll(functionRegistryUtil.getExpectedDimensionsFor(Test_HasLegOfCompetitorContext.class)); - assertThat(functionProvider.getDimensionsFor(dataRetrieverChainDefinition), is(expectedDimensions)); + assertThat(functionManager.getDimensionsFor(dataRetrieverChainDefinition), is(expectedDimensions)); } @SuppressWarnings("unchecked") @@ -113,21 +109,18 @@ public class TestFunctionProvider { @Test public void testGetStatisticsForType() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); Collection> expectedStatistics = functionRegistryUtil.getExpectedStatisticsFor(Test_HasLegOfCompetitorContext.class); - assertThat(functionProvider.getStatisticsFor(Test_HasLegOfCompetitorContext.class), is(expectedStatistics)); - assertThat(functionProvider.getStatisticsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedStatistics)); + assertThat(functionManager.getStatisticsFor(Test_HasLegOfCompetitorContext.class), is(expectedStatistics)); + assertThat(functionManager.getStatisticsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedStatistics)); } @Test public void testGetAllFunctionsForType() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); - Collection> expectedFunctions = functionRegistryUtil.getExpectedDimensionsFor(Test_HasRaceContext.class); expectedFunctions.addAll(functionRegistryUtil.getExpectedDimensionsFor(Test_HasLegOfCompetitorContext.class)); expectedFunctions.addAll(functionRegistryUtil.getExpectedStatisticsFor(Test_HasLegOfCompetitorContext.class)); - assertThat(functionProvider.getFunctionsFor(Test_HasLegOfCompetitorContext.class), is(expectedFunctions)); - assertThat(functionProvider.getFunctionsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedFunctions)); + assertThat(functionManager.getFunctionsFor(Test_HasLegOfCompetitorContext.class), is(expectedFunctions)); + assertThat(functionManager.getFunctionsFor(Test_HasLegOfCompetitorContextImpl.class), is(expectedFunctions)); } @Test @@ -140,9 +133,8 @@ public class TestFunctionProvider { FunctionDTO getRegattaNameDTO = FunctionTestsUtil.getFunctionDTOFactory().createFunctionDTO(getRegattaName, stringMessages, Locale.ENGLISH); - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); @SuppressWarnings("unchecked") // Hamcrest requires type matching of actual and expected type, so the Functions have to be specific (without ) - Function providedFunction = (Function) functionProvider.getFunctionForDTO(getRegattaNameDTO); + Function providedFunction = (Function) functionManager.getFunctionForDTO(getRegattaNameDTO); assertThat(providedFunction, is(getRegattaName)); } @@ -152,14 +144,12 @@ public class TestFunctionProvider { Function illegalDimension = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(illegalDimensionMethod); FunctionDTO illegalDimensionDTO = FunctionTestsUtil.getFunctionDTOFactory().createFunctionDTO(illegalDimension, stringMessages, Locale.ENGLISH); - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); - assertThat(functionProvider.getFunctionForDTO(illegalDimensionDTO), is(nullValue())); + assertThat(functionManager.getFunctionForDTO(illegalDimensionDTO), is(nullValue())); } @Test public void testGetFunctionForNullDTO() { - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); - assertThat(functionProvider.getFunctionForDTO(null), is(nullValue())); + assertThat(functionManager.getFunctionForDTO(null), is(nullValue())); } @Test @@ -167,14 +157,13 @@ public class TestFunctionProvider { Method nullaryDimensionMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "dimension"); Function nullaryDimension = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(nullaryDimensionMethod); - FunctionRegistry functionRegistry = new SimpleFunctionRegistry(); - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); + FunctionProvider functionProvider = new FunctionManager(); assertThat(functionProvider.getParameterProviderFor(nullaryDimension), is(ParameterProvider.NULL)); } @Test public void testGetPrameterProviderForFunctionsWithParameters() { - FunctionRegistry functionRegistry = new SimpleFunctionRegistry(); + FunctionManager functionManager = new FunctionManager(); ParameterProvider intParameterProvider = new ParameterProvider() { @Override public Object[] getParameters() { @@ -188,30 +177,28 @@ public class TestFunctionProvider { return parameterTypes; } }; - functionRegistry.registerParameterProvider(intParameterProvider); + functionManager.registerParameterProvider(intParameterProvider); LocalizationParameterProvider stringMessagesParameterProvider = new LocalizationParameterProvider(Locale.ENGLISH, stringMessages); - functionRegistry.registerParameterProvider(stringMessagesParameterProvider); - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); + functionManager.registerParameterProvider(stringMessagesParameterProvider); Method incrementMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "increment", int.class); Function increment = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(incrementMethod); - assertThat(functionProvider.getParameterProviderFor(increment), is(intParameterProvider)); + assertThat(functionManager.getParameterProviderFor(increment), is(intParameterProvider)); Method getLocalizedNameMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "getLocalizedName", Locale.class, DataMiningStringMessages.class); Function getLocalizedName = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(getLocalizedNameMethod); - assertThat(functionProvider.getParameterProviderFor(getLocalizedName), is(stringMessagesParameterProvider)); + assertThat(functionManager.getParameterProviderFor(getLocalizedName), is(stringMessagesParameterProvider)); } @Test public void testGetParameterProviderForFunctionWithoutAnAvailableParameterProvider() { - FunctionRegistry functionRegistry = new SimpleFunctionRegistry(); + FunctionManager functionManager = new FunctionManager(); //Adding a wrong ParameterProvider to increase code coverage - functionRegistry.registerParameterProvider(new LocalizationParameterProvider(Locale.ENGLISH, stringMessages)); - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); + functionManager.registerParameterProvider(new LocalizationParameterProvider(Locale.ENGLISH, stringMessages)); Method incrementMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "increment", int.class); Function increment = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(incrementMethod); - assertThat(functionProvider.getParameterProviderFor(increment), nullValue()); + assertThat(functionManager.getParameterProviderFor(increment), nullValue()); } } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestSimpleFunctionRegistry.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java similarity index 96% rename from java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestSimpleFunctionRegistry.java rename to java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java index 63a76a5b490..21f3891c5f9 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestSimpleFunctionRegistry.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java @@ -20,7 +20,7 @@ import com.sap.sse.datamining.test.functions.test_classes.Test_ExternalLibraryCl import com.sap.sse.datamining.test.util.ExpectedFunctionRegistryUtil; -public class TestSimpleFunctionRegistry { +public class TestFunctionManagerAsFunctionRegistry { private static ExpectedFunctionRegistryUtil expectedFunctionRegistryUtil; @@ -45,7 +45,7 @@ public class TestSimpleFunctionRegistry { @Test public void testRegistration() throws NoSuchMethodException, SecurityException { - FunctionRegistry registry = new SimpleFunctionRegistry(); + FunctionRegistry registry = new FunctionManager(); registry.registerAllWithInternalFunctionPolicy(internalClassesToScan); registry.registerAllWithExternalFunctionPolicy(externalClassesToScan); @@ -63,7 +63,7 @@ public class TestSimpleFunctionRegistry { @Test public void testUnregistration() { - FunctionRegistry registry = new SimpleFunctionRegistry(); + FunctionRegistry registry = new FunctionManager(); registry.registerAllWithInternalFunctionPolicy(internalClassesToScan); Collection> expectedDimensions = new HashSet<>(); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java index 83e2863422f..ff115301ad3 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java @@ -6,7 +6,7 @@ import java.util.Collection; import com.sap.sse.datamining.factories.ProcessorFactory; import com.sap.sse.datamining.functions.FunctionProvider; -import com.sap.sse.datamining.impl.functions.RegistryFunctionProvider; +import com.sap.sse.datamining.impl.functions.FunctionManager; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_Boat; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_BoatClass; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_Competitor; @@ -24,7 +24,7 @@ import com.sap.sse.datamining.test.functions.registry.test_classes.impl.Test_Tea public final class ComponentTestsUtil { - private final static FunctionProvider emptyFunctionProvider = new RegistryFunctionProvider(new ArrayList<>()); + private final static FunctionProvider emptyFunctionProvider = new FunctionManager(); private final static ProcessorFactory processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor(), emptyFunctionProvider); public static ProcessorFactory getProcessorFactory() { diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningActivator.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningActivator.java index 10348a59c42..b86a4a8dedc 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningActivator.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningActivator.java @@ -18,10 +18,7 @@ import com.sap.sse.datamining.DataMiningBundleService; import com.sap.sse.datamining.DataMiningServer; import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.ModifiableDataMiningServer; -import com.sap.sse.datamining.functions.FunctionProvider; -import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.impl.functions.RegistryFunctionProvider; -import com.sap.sse.datamining.impl.functions.SimpleFunctionRegistry; +import com.sap.sse.datamining.impl.functions.FunctionManager; import com.sap.sse.datamining.impl.i18n.DataMiningStringMessagesImpl; public class DataMiningActivator implements BundleActivator { @@ -40,10 +37,9 @@ public class DataMiningActivator implements BundleActivator { ExecutorService executor = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, 60, TimeUnit.SECONDS, new LinkedBlockingQueue()); - FunctionRegistry functionRegistry = new SimpleFunctionRegistry(); - FunctionProvider functionProvider = new RegistryFunctionProvider(functionRegistry); + FunctionManager functionManager = new FunctionManager(); DataRetrieverChainDefinitionRegistry dataRetrieverChainDefinitionRegistry = new SimpleDataRetrieverChainDefinitionRegistry(); - dataMiningServer = new DataMiningServerImpl(executor, functionRegistry, functionProvider, dataRetrieverChainDefinitionRegistry); + dataMiningServer = new DataMiningServerImpl(executor, functionManager, functionManager, dataRetrieverChainDefinitionRegistry); dataMiningServer.addStringMessages(new DataMiningStringMessagesImpl(STRING_MESSAGES_BASE_NAME, DataMiningActivator.class.getClassLoader())); serviceRegistrations = new HashSet<>(); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java new file mode 100644 index 00000000000..bda7b355749 --- /dev/null +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java @@ -0,0 +1,420 @@ +package com.sap.sse.datamining.impl.functions; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sse.datamining.DataRetrieverChainDefinition; +import com.sap.sse.datamining.components.FilterCriterion; +import com.sap.sse.datamining.factories.FunctionDTOFactory; +import com.sap.sse.datamining.factories.FunctionFactory; +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.FunctionProvider; +import com.sap.sse.datamining.functions.FunctionRegistry; +import com.sap.sse.datamining.functions.ParameterProvider; +import com.sap.sse.datamining.impl.DataRetrieverTypeWithInformation; +import com.sap.sse.datamining.impl.functions.criterias.FunctionMatchesDTOFilterCriterion; +import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidConnectorFilterCriterion; +import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidDimensionFilterCriterion; +import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidExternalFunctionFilterCriterion; +import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidStatisticFilterCriterion; +import com.sap.sse.datamining.shared.dto.FunctionDTO; + +public class FunctionManager implements FunctionRegistry, FunctionProvider { + + private enum FunctionRetrievalStrategies { + All { + @Override + public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { + return registry.getAllFunctionsOf(declaringType); + } + }, + Dimensions { + @Override + public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { + return registry.getDimensionsOf(declaringType); + } + }, + Statistics { + @Override + public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { + return registry.getStatisticsOf(declaringType); + } + }; + + public abstract Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry); + + } + + private static final Logger LOGGER = Logger.getLogger(FunctionManager.class.getName()); + + private final FilterCriterion isValidDimension = new MethodIsValidDimensionFilterCriterion(); + private final FilterCriterion isValidStatistic = new MethodIsValidStatisticFilterCriterion(); + private final FilterCriterion isValidConnector = new MethodIsValidConnectorFilterCriterion(); + private final FilterCriterion isValidExternalFunction = new MethodIsValidExternalFunctionFilterCriterion(); + + private final FunctionFactory functionFactory; + private final FunctionDTOFactory functionDTOFactory; + + private final Map, Set>> statistics; + private final Map, Set>> dimensions; + private final Map, Set>> externalFunctions; + + private final Collection, Set>>> functionMaps; + + private final Collection parameterProviders; + + public FunctionManager() { + functionDTOFactory = new FunctionDTOFactory(); + functionFactory = new FunctionFactory(); + + statistics = new HashMap<>(); + dimensions = new HashMap<>(); + externalFunctions = new HashMap<>(); + + functionMaps = new ArrayList<>(); + functionMaps.add(statistics); + functionMaps.add(dimensions); + functionMaps.add(externalFunctions); + + parameterProviders = new HashSet<>(); + } + + @Override + public void registerAllWithInternalFunctionPolicy(Iterable> internalClassesToScan) { + for (Class internalClass : internalClassesToScan) { + scanInternalClass(internalClass); + } + } + + private void scanInternalClass(Class internalClass) { + scanInternalClass(internalClass, new ArrayList>()); + } + + private void scanInternalClass(Class internalClass, List> previousFunctions) { + for (Method method : internalClass.getMethods()) { + if (isValidDimension.matches(method) || isValidStatistic.matches(method)) { + registerFunction(previousFunctions, method); + continue; + } + + if (isValidConnector.matches(method)) { + handleConnectorMethod(method, previousFunctions); + } + } + } + + private void registerFunction(List> previousFunctions, Method method) { + Function function = functionFactory.createMethodWrappingFunction(method); + if (!previousFunctions.isEmpty()) { + function = functionFactory.createCompoundFunction(null, previousFunctions, function); + } + + if (function.isDimension()) { + addDimension(function); + } else { + addStatistic(function); + } + } + + private void addDimension(Function dimension) { + Class declaringType = dimension.getDeclaringType(); + if (!dimensions.containsKey(declaringType)) { + dimensions.put(declaringType, new HashSet>()); + } + dimensions.get(declaringType).add(dimension); + } + + private void addStatistic(Function statistic) { + Class declaringType = statistic.getDeclaringType(); + if (!statistics.containsKey(declaringType)) { + statistics.put(declaringType, new HashSet>()); + } + statistics.get(declaringType).add(statistic); + } + + private void handleConnectorMethod(Method method, List> previousFunctions) { + Function function = functionFactory.createMethodWrappingFunction(method); + Class returnType = method.getReturnType(); + List> previousFunctionsClone = new ArrayList<>(previousFunctions); + previousFunctionsClone.add(function); + scanInternalClass(returnType, previousFunctionsClone); + } + + @Override + public void registerAllWithExternalFunctionPolicy(Iterable> externalClassesToScan) { + for (Class externalClass : externalClassesToScan) { + for (Method method : externalClass.getMethods()) { + if (isValidExternalFunction.matches(method)) { + Function function = functionFactory.createMethodWrappingFunction(method); + addExternalFunction(function); + } + } + } + } + + private void addExternalFunction(Function function) { + Class declaringType = function.getDeclaringType(); + if (!externalFunctions.containsKey(declaringType)) { + externalFunctions.put(declaringType, new HashSet>()); + } + externalFunctions.get(declaringType).add(function); + } + + @Override + public void registerParameterProvider(ParameterProvider parameterProvider) { + parameterProviders.add(parameterProvider); + } + + @Override + public void unregisterAllFunctionsOf(Iterable> classesToUnregister) { + for (Class classToUnregister : classesToUnregister) { + unregisterAllFunctionsOf(classToUnregister); + } + } + + private void unregisterAllFunctionsOf(Class classToUnregister) { + for (Map, Set>> functionMap : functionMaps) { + functionMap.remove(classToUnregister); + } + } + + @Override + public void unregisterParameterProvider(ParameterProvider parameterProvider) { + parameterProviders.remove(parameterProvider); + } + + @Override + public Collection> getAllFunctions() { + Collection> allFunctions = new HashSet<>(); + for (Map, Set>> functionMap : functionMaps) { + allFunctions.addAll(asSet(functionMap)); + } + return allFunctions; + } + + @Override + public Collection> getAllFunctionsOf(Class declaringType) { + Collection> allFunctions = new HashSet<>(); + for (Map, Set>> functionMap : functionMaps) { + Collection> functions = functionMap.get(declaringType); + if (functions != null) { + allFunctions.addAll(functions); + } + } + return allFunctions; + } + + @Override + public Collection> getStatistics() { + return asSet(statistics); + } + + @Override + public Collection> getStatisticsOf(Class declaringType) { + return statistics.get(declaringType); + } + + @Override + public Collection> getDimensions() { + return asSet(dimensions); + } + + @Override + public Collection> getDimensionsOf(Class declaringType) { + return dimensions.get(declaringType); + } + + @Override + public Collection> getExternalFunctions() { + return asSet(externalFunctions); + } + + @Override + public Collection> getExternalFunctionsOf(Class declaringType) { + return externalFunctions.get(declaringType); + } + + private Collection> asSet(Map>> map) { + Collection> set = new HashSet<>(); + for (Entry>> entry : map.entrySet()) { + set.addAll(entry.getValue()); + } + return set; + } + + @Override + public Iterable getAllParameterProviders() { + return parameterProviders; + } + + @Override + public Collection> getAllStatistics() { + return getStatistics(); + } + + @Override + public Collection> getFunctionsFor(Class sourceType) { + return getFunctionsFor(sourceType, FunctionRetrievalStrategies.All); + } + + @Override + public Collection> getDimensionsFor(Class sourceType) { + return getFunctionsFor(sourceType, FunctionRetrievalStrategies.Dimensions); + } + + @Override + public Collection> getDimensionsFor(DataRetrieverChainDefinition dataRetrieverChainDefinition) { + Collection> dimensions = new HashSet<>(); + for (DataRetrieverTypeWithInformation dataRetrieverTypeWithInformation : dataRetrieverChainDefinition.getDataRetrieverTypesWithInformation()) { + dimensions.addAll(getDimensionsFor(dataRetrieverTypeWithInformation.getRetrievedDataType())); + } + return dimensions; + } + + @Override + public Collection> getStatisticsFor(Class sourceType) { + return getFunctionsFor(sourceType, FunctionRetrievalStrategies.Statistics); + } + + private Collection> getFunctionsFor(Class sourceType, FunctionRetrievalStrategies retrievalStrategy) { + Collection> typesToRetrieve = getSupertypesOf(sourceType); + typesToRetrieve.add(sourceType); + return getFunctionsFor(typesToRetrieve, retrievalStrategy); + } + + private Collection> getFunctionsFor(Collection> typesToRetrieve, FunctionRetrievalStrategies retrievalStrategy) { + Collection> functions = new HashSet<>(); + for (Class typeToRetrieve : typesToRetrieve) { + Collection> typeSpecificFunctions = retrievalStrategy.retrieveFunctions(typeToRetrieve, this); + if (typeSpecificFunctions != null) { + functions.addAll(typeSpecificFunctions); + } + } + return functions; + } + + private Collection> getSupertypesOf(Class type) { + Collection> supertypes = new HashSet<>(); + + supertypes.addAll(getInterfacesOf(type)); + if (isSuperclassValid(type)) { + supertypes.add(type.getSuperclass()); + } + + supertypes.addAll(getSupertypesOf(supertypes)); + return supertypes; + } + + private Collection> getInterfacesOf(Class type) { + return Arrays.asList(type.getInterfaces()); + } + + private boolean isSuperclassValid(Class subType) { + return subType.getSuperclass() != null && !subType.getSuperclass().equals(Object.class); + } + + private Collection> getSupertypesOf(Collection> types) { + Collection> supertypes = new HashSet<>(); + boolean supertypeAdded; + do { + Collection> supertypesToAdd = getSupertypesToAdd(types); + supertypeAdded = supertypes.addAll(supertypesToAdd); + } while (supertypeAdded); + return supertypes; + } + + private Collection> getSupertypesToAdd(Collection> types) { + Collection> supertypesToAdd = new HashSet<>(); + for (Class supertype : types) { + supertypesToAdd.addAll(getSupertypesOf(supertype)); + } + return supertypesToAdd; + } + + @Override + public Function getFunctionForDTO(FunctionDTO functionDTO) { + if (functionDTO == null) { + return null; + } + + Collection> functionsMatchingDTO = getFunctionsForDTO(functionDTO); + if (moreThanOneFunctionMatchedDTO(functionsMatchingDTO)) { + logThatMoreThanOneFunctionMatchedDTO(functionDTO, functionsMatchingDTO); + } + + return getFunctionToReturn(functionsMatchingDTO); + } + + private Collection> getFunctionsForDTO(FunctionDTO functionDTO) { + Collection> functionsMatchingDTO = new HashSet<>(); + FilterCriterion> functionDTOFilterCriteria = new FunctionMatchesDTOFilterCriterion(functionDTOFactory, functionDTO); + for (Function function : getAllFunctions()) { + if (functionDTOFilterCriteria.matches(function)) { + functionsMatchingDTO.add(function); + } + } + return functionsMatchingDTO; + } + + private boolean moreThanOneFunctionMatchedDTO(Collection> functionsMatchingDTO) { + return functionsMatchingDTO.size() > 1; + } + + private void logThatMoreThanOneFunctionMatchedDTO(FunctionDTO functionDTO, Collection> functionsMatchingDTO) { + LOGGER.log(Level.FINER, "More than on registered function matched the function DTO '" + functionDTO.toString() + "'"); + for (Function function : functionsMatchingDTO) { + LOGGER.log(Level.FINEST, "The function '" + function.toString() + "' matched the function DTO '" + functionDTO.toString() + "'"); + } + } + + private Function getFunctionToReturn(Collection> functionsMatchingDTO) { + return functionsMatchingDTO.isEmpty() ? null : functionsMatchingDTO.iterator().next(); + } + + @Override + public ParameterProvider getParameterProviderFor(Function function) { + Iterable> functionParameterTypes = function.getParameters(); + if (!functionParameterTypes.iterator().hasNext()) { + return ParameterProvider.NULL; + } + + for (ParameterProvider parameterProvider : getAllParameterProviders()) { + if (parametersAreMatching(function, parameterProvider)) { + return parameterProvider; + } + } + + return null; + } + + private boolean parametersAreMatching(Function function, ParameterProvider parameterProvider) { + Iterator> functionParametersIterator = function.getParameters().iterator(); + Iterator> parameterProviderIterator = parameterProvider.getParameterTypes().iterator(); + + while (functionParametersIterator.hasNext() && parameterProviderIterator.hasNext()) { + Class functionParameter = functionParametersIterator.next(); + Class parameterProviderParameter = parameterProviderIterator.next(); + if (!functionParameter.isAssignableFrom(parameterProviderParameter)) { + return false; + } + } + + if (functionParametersIterator.hasNext() || parameterProviderIterator.hasNext()) { + return false; + } + + return true; + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/RegistryFunctionProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/RegistryFunctionProvider.java deleted file mode 100644 index a4dd669c8a6..00000000000 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/RegistryFunctionProvider.java +++ /dev/null @@ -1,230 +0,0 @@ -package com.sap.sse.datamining.impl.functions; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.sap.sse.datamining.DataRetrieverChainDefinition; -import com.sap.sse.datamining.components.FilterCriterion; -import com.sap.sse.datamining.factories.FunctionDTOFactory; -import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionProvider; -import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.functions.ParameterProvider; -import com.sap.sse.datamining.impl.DataRetrieverTypeWithInformation; -import com.sap.sse.datamining.impl.functions.criterias.FunctionMatchesDTOFilterCriterion; -import com.sap.sse.datamining.shared.dto.FunctionDTO; - -public class RegistryFunctionProvider implements FunctionProvider { - - private enum FunctionRetrievalStrategies { - All { - @Override - public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { - return registry.getAllFunctionsOf(declaringType); - } - }, - Dimensions { - @Override - public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { - return registry.getDimensionsOf(declaringType); - } - }, - Statistics { - @Override - public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { - return registry.getStatisticsOf(declaringType); - } - }; - - public abstract Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry); - - } - - private static final Logger LOGGER = Logger.getLogger(RegistryFunctionProvider.class.getName()); - - private final FunctionDTOFactory functionDTOFactory; - private final Collection functionRegistries; - - public RegistryFunctionProvider(FunctionRegistry... functionRegistry) { - this(Arrays.asList(functionRegistry)); - } - - public RegistryFunctionProvider(Collection functionRegistries) { - functionDTOFactory = new FunctionDTOFactory(); - this.functionRegistries = new HashSet<>(functionRegistries); - } - - @Override - public Collection> getAllStatistics() { - Collection> allStatistics = new HashSet<>(); - for (FunctionRegistry registry : functionRegistries) { - allStatistics.addAll(registry.getStatistics()); - } - return allStatistics; - } - - @Override - public Collection> getFunctionsFor(Class sourceType) { - return getFunctionsFor(sourceType, FunctionRetrievalStrategies.All); - } - - @Override - public Collection> getDimensionsFor(Class sourceType) { - return getFunctionsFor(sourceType, FunctionRetrievalStrategies.Dimensions); - } - - @Override - public Collection> getDimensionsFor(DataRetrieverChainDefinition dataRetrieverChainDefinition) { - Collection> dimensions = new HashSet<>(); - for (DataRetrieverTypeWithInformation dataRetrieverTypeWithInformation : dataRetrieverChainDefinition.getDataRetrieverTypesWithInformation()) { - dimensions.addAll(getDimensionsFor(dataRetrieverTypeWithInformation.getRetrievedDataType())); - } - return dimensions; - } - - @Override - public Collection> getStatisticsFor(Class sourceType) { - return getFunctionsFor(sourceType, FunctionRetrievalStrategies.Statistics); - } - - private Collection> getFunctionsFor(Class sourceType, FunctionRetrievalStrategies retrievalStrategy) { - Collection> typesToRetrieve = getSupertypesOf(sourceType); - typesToRetrieve.add(sourceType); - return getFunctionsFor(typesToRetrieve, retrievalStrategy); - } - - private Collection> getFunctionsFor(Collection> typesToRetrieve, FunctionRetrievalStrategies retrievalStrategy) { - Collection> functions = new HashSet<>(); - for (FunctionRegistry functionRegistry : functionRegistries) { - for (Class typeToRetrieve : typesToRetrieve) { - Collection> typeSpecificFunctions = retrievalStrategy.retrieveFunctions(typeToRetrieve, functionRegistry); - if (typeSpecificFunctions != null) { - functions.addAll(typeSpecificFunctions); - } - } - } - return functions; - } - - private Collection> getSupertypesOf(Class type) { - Collection> supertypes = new HashSet<>(); - - supertypes.addAll(getInterfacesOf(type)); - if (isSuperclassValid(type)) { - supertypes.add(type.getSuperclass()); - } - - supertypes.addAll(getSupertypesOf(supertypes)); - return supertypes; - } - - private Collection> getInterfacesOf(Class type) { - return Arrays.asList(type.getInterfaces()); - } - - private boolean isSuperclassValid(Class subType) { - return subType.getSuperclass() != null && !subType.getSuperclass().equals(Object.class); - } - - private Collection> getSupertypesOf(Collection> types) { - Collection> supertypes = new HashSet<>(); - boolean supertypeAdded; - do { - Collection> supertypesToAdd = getSupertypesToAdd(types); - supertypeAdded = supertypes.addAll(supertypesToAdd); - } while (supertypeAdded); - return supertypes; - } - - private Collection> getSupertypesToAdd(Collection> types) { - Collection> supertypesToAdd = new HashSet<>(); - for (Class supertype : types) { - supertypesToAdd.addAll(getSupertypesOf(supertype)); - } - return supertypesToAdd; - } - - @Override - public Function getFunctionForDTO(FunctionDTO functionDTO) { - if (functionDTO == null) { - return null; - } - - Collection> functionsMatchingDTO = getFunctionsForDTO(functionDTO); - if (moreThanOneFunctionMatchedDTO(functionsMatchingDTO)) { - logThatMoreThanOneFunctionMatchedDTO(functionDTO, functionsMatchingDTO); - } - - return getFunctionToReturn(functionsMatchingDTO); - } - - private Collection> getFunctionsForDTO(FunctionDTO functionDTO) { - Collection> functionsMatchingDTO = new HashSet<>(); - FilterCriterion> functionDTOFilterCriteria = new FunctionMatchesDTOFilterCriterion(functionDTOFactory, functionDTO); - for (FunctionRegistry functionRegistry : functionRegistries) { - for (Function function : functionRegistry.getAllFunctions()) { - if (functionDTOFilterCriteria.matches(function)) { - functionsMatchingDTO.add(function); - } - } - } - return functionsMatchingDTO; - } - - private boolean moreThanOneFunctionMatchedDTO(Collection> functionsMatchingDTO) { - return functionsMatchingDTO.size() > 1; - } - - private void logThatMoreThanOneFunctionMatchedDTO(FunctionDTO functionDTO, Collection> functionsMatchingDTO) { - LOGGER.log(Level.FINER, "More than on registered function matched the function DTO '" + functionDTO.toString() + "'"); - for (Function function : functionsMatchingDTO) { - LOGGER.log(Level.FINEST, "The function '" + function.toString() + "' matched the function DTO '" + functionDTO.toString() + "'"); - } - } - - private Function getFunctionToReturn(Collection> functionsMatchingDTO) { - return functionsMatchingDTO.isEmpty() ? null : functionsMatchingDTO.iterator().next(); - } - - @Override - public ParameterProvider getParameterProviderFor(Function function) { - Iterable> functionParameterTypes = function.getParameters(); - if (!functionParameterTypes.iterator().hasNext()) { - return ParameterProvider.NULL; - } - - for (FunctionRegistry functionRegistry : functionRegistries) { - for (ParameterProvider parameterProvider : functionRegistry.getAllParameterProviders()) { - if (parametersAreMatching(function, parameterProvider)) { - return parameterProvider; - } - } - } - - return null; - } - - private boolean parametersAreMatching(Function function, ParameterProvider parameterProvider) { - Iterator> functionParametersIterator = function.getParameters().iterator(); - Iterator> parameterProviderIterator = parameterProvider.getParameterTypes().iterator(); - - while (functionParametersIterator.hasNext() && parameterProviderIterator.hasNext()) { - Class functionParameter = functionParametersIterator.next(); - Class parameterProviderParameter = parameterProviderIterator.next(); - if (!functionParameter.isAssignableFrom(parameterProviderParameter)) { - return false; - } - } - - if (functionParametersIterator.hasNext() || parameterProviderIterator.hasNext()) { - return false; - } - - return true; - } - -} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java deleted file mode 100644 index 3d8d4e70b7d..00000000000 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleFunctionRegistry.java +++ /dev/null @@ -1,223 +0,0 @@ -package com.sap.sse.datamining.impl.functions; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import com.sap.sse.datamining.components.FilterCriterion; -import com.sap.sse.datamining.factories.FunctionFactory; -import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.functions.ParameterProvider; -import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidConnectorFilterCriterion; -import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidDimensionFilterCriterion; -import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidExternalFunctionFilterCriterion; -import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidStatisticFilterCriterion; - -public class SimpleFunctionRegistry implements FunctionRegistry { - - private final FilterCriterion isValidDimension = new MethodIsValidDimensionFilterCriterion(); - private final FilterCriterion isValidStatistic = new MethodIsValidStatisticFilterCriterion(); - private final FilterCriterion isValidConnector = new MethodIsValidConnectorFilterCriterion(); - private final FilterCriterion isValidExternalFunction = new MethodIsValidExternalFunctionFilterCriterion(); - - private final FunctionFactory functionFactory; - - private final Map, Set>> statistics; - private final Map, Set>> dimensions; - private final Map, Set>> externalFunctions; - - private final Collection, Set>>> functionMaps; - - private final Collection parameterProviders; - - public SimpleFunctionRegistry() { - functionFactory = new FunctionFactory(); - - statistics = new HashMap<>(); - dimensions = new HashMap<>(); - externalFunctions = new HashMap<>(); - - functionMaps = new ArrayList<>(); - functionMaps.add(statistics); - functionMaps.add(dimensions); - functionMaps.add(externalFunctions); - - parameterProviders = new HashSet<>(); - } - - @Override - public void registerAllWithInternalFunctionPolicy(Iterable> internalClassesToScan) { - for (Class internalClass : internalClassesToScan) { - scanInternalClass(internalClass); - } - } - - private void scanInternalClass(Class internalClass) { - scanInternalClass(internalClass, new ArrayList>()); - } - - private void scanInternalClass(Class internalClass, List> previousFunctions) { - for (Method method : internalClass.getMethods()) { - if (isValidDimension.matches(method) || isValidStatistic.matches(method)) { - registerFunction(previousFunctions, method); - continue; - } - - if (isValidConnector.matches(method)) { - handleConnectorMethod(method, previousFunctions); - } - } - } - - private void registerFunction(List> previousFunctions, Method method) { - Function function = functionFactory.createMethodWrappingFunction(method); - if (!previousFunctions.isEmpty()) { - function = functionFactory.createCompoundFunction(null, previousFunctions, function); - } - - if (function.isDimension()) { - addDimension(function); - } else { - addStatistic(function); - } - } - - private void addDimension(Function dimension) { - Class declaringType = dimension.getDeclaringType(); - if (!dimensions.containsKey(declaringType)) { - dimensions.put(declaringType, new HashSet>()); - } - dimensions.get(declaringType).add(dimension); - } - - private void addStatistic(Function statistic) { - Class declaringType = statistic.getDeclaringType(); - if (!statistics.containsKey(declaringType)) { - statistics.put(declaringType, new HashSet>()); - } - statistics.get(declaringType).add(statistic); - } - - private void handleConnectorMethod(Method method, List> previousFunctions) { - Function function = functionFactory.createMethodWrappingFunction(method); - Class returnType = method.getReturnType(); - List> previousFunctionsClone = new ArrayList<>(previousFunctions); - previousFunctionsClone.add(function); - scanInternalClass(returnType, previousFunctionsClone); - } - - @Override - public void registerAllWithExternalFunctionPolicy(Iterable> externalClassesToScan) { - for (Class externalClass : externalClassesToScan) { - for (Method method : externalClass.getMethods()) { - if (isValidExternalFunction.matches(method)) { - Function function = functionFactory.createMethodWrappingFunction(method); - addExternalFunction(function); - } - } - } - } - - private void addExternalFunction(Function function) { - Class declaringType = function.getDeclaringType(); - if (!externalFunctions.containsKey(declaringType)) { - externalFunctions.put(declaringType, new HashSet>()); - } - externalFunctions.get(declaringType).add(function); - } - - @Override - public void registerParameterProvider(ParameterProvider parameterProvider) { - parameterProviders.add(parameterProvider); - } - - @Override - public void unregisterAllFunctionsOf(Iterable> classesToUnregister) { - for (Class classToUnregister : classesToUnregister) { - unregisterAllFunctionsOf(classToUnregister); - } - } - - private void unregisterAllFunctionsOf(Class classToUnregister) { - for (Map, Set>> functionMap : functionMaps) { - functionMap.remove(classToUnregister); - } - } - - @Override - public void unregisterParameterProvider(ParameterProvider parameterProvider) { - parameterProviders.remove(parameterProvider); - } - - @Override - public Collection> getAllFunctions() { - Collection> allFunctions = new HashSet<>(); - for (Map, Set>> functionMap : functionMaps) { - allFunctions.addAll(asSet(functionMap)); - } - return allFunctions; - } - - @Override - public Collection> getAllFunctionsOf(Class declaringType) { - Collection> allFunctions = new HashSet<>(); - for (Map, Set>> functionMap : functionMaps) { - Collection> functions = functionMap.get(declaringType); - if (functions != null) { - allFunctions.addAll(functions); - } - } - return allFunctions; - } - - @Override - public Collection> getStatistics() { - return asSet(statistics); - } - - @Override - public Collection> getStatisticsOf(Class declaringType) { - return statistics.get(declaringType); - } - - @Override - public Collection> getDimensions() { - return asSet(dimensions); - } - - @Override - public Collection> getDimensionsOf(Class declaringType) { - return dimensions.get(declaringType); - } - - @Override - public Collection> getExternalFunctions() { - return asSet(externalFunctions); - } - - @Override - public Collection> getExternalFunctionsOf(Class declaringType) { - return externalFunctions.get(declaringType); - } - - private Collection> asSet(Map>> map) { - Collection> set = new HashSet<>(); - for (Entry>> entry : map.entrySet()) { - set.addAll(entry.getValue()); - } - return set; - } - - @Override - public Iterable getAllParameterProviders() { - return parameterProviders; - } - -} From 93a3039217e25168d1b0ca11e0c5805955dd7746 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Mon, 29 Dec 2014 13:16:53 +0100 Subject: [PATCH 007/264] Fixed a problem with the LocalizationParameterProviders --- .../sap/sse/datamining/impl/i18n/TestI18N.java | 16 ++++++++++++++++ .../i18n/DataMiningStringMessages.java | 18 ++++++++++-------- .../datamining/impl/DataMiningServerImpl.java | 12 ++++++++---- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java index 075e53155ea..de2729d4e0f 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java @@ -4,8 +4,10 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import java.util.HashSet; import java.util.Locale; import java.util.MissingResourceException; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -66,6 +68,20 @@ public class TestI18N { assertThat(DataMiningStringMessages.Util.getLocaleFor("Unsupported locale info name"), is(Locale.ENGLISH)); } + @Test + public void testGetSupportedLocales() { + Set supportedLocales = new HashSet<>(); + for (Locale locale : DataMiningStringMessages.Util.getSupportedLocales()) { + supportedLocales.add(locale); + } + + Set expectedSupportedLocales = new HashSet<>(); + expectedSupportedLocales.add(Locale.GERMAN); + expectedSupportedLocales.add(Locale.ENGLISH); + + assertThat(supportedLocales, is(expectedSupportedLocales)); + } + @Test(expected=MissingResourceException.class) public void testGettingAMessageForAMissingKeyFromSimpleStringMessages() { testStringMessages.get(Locale.ENGLISH, "$%&MissingKey/()"); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/i18n/DataMiningStringMessages.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/i18n/DataMiningStringMessages.java index 0da3a28e2d9..3700b79d957 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/i18n/DataMiningStringMessages.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/i18n/DataMiningStringMessages.java @@ -14,25 +14,27 @@ public interface DataMiningStringMessages { public static final class Util { private static boolean supportedLocalesHaveBeenInitialized = false; - private static final String DEFAULT_LOCALE_NAME = "default"; private static final Locale DEFAULT_LOCALE = Locale.ENGLISH; private static final Map supportedLocalesMappedByLocaleInfo = new HashMap<>(); public static Locale getLocaleFor(String localeInfoName) { - if (!supportedLocalesHaveBeenInitialized) { - initializeSupportedLocales(); - } - - Locale locale = Util.supportedLocalesMappedByLocaleInfo.get(localeInfoName); + Locale locale = getSupportedLocalesMap().get(localeInfoName); return locale != null ? locale : DEFAULT_LOCALE; } public static Iterable getSupportedLocales() { - return supportedLocalesMappedByLocaleInfo.values(); + return getSupportedLocalesMap().values(); + } + + private static Map getSupportedLocalesMap() { + if (!supportedLocalesHaveBeenInitialized) { + initializeSupportedLocales(); + } + + return supportedLocalesMappedByLocaleInfo; } private static void initializeSupportedLocales() { - supportedLocalesMappedByLocaleInfo.put(DEFAULT_LOCALE_NAME, DEFAULT_LOCALE); supportedLocalesMappedByLocaleInfo.put("en", Locale.ENGLISH); supportedLocalesMappedByLocaleInfo.put("de", Locale.GERMAN); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java index 8e7d6cda6db..2a29f3e4f33 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java @@ -48,10 +48,14 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { } private void initializeStringMessagesParameterProvider() { - for (Locale locale : DataMiningStringMessages.Util.getSupportedLocales()) { - ParameterProvider stringMessagesParameterProvider = new LocalizationParameterProvider(locale, getStringMessages()); - registerParameterProvider(stringMessagesParameterProvider); - } + //TODO enable after improved ParameterProvider handling +// for (Locale locale : DataMiningStringMessages.Util.getSupportedLocales()) { +// ParameterProvider stringMessagesParameterProvider = new LocalizationParameterProvider(locale, getStringMessages()); +// registerParameterProvider(stringMessagesParameterProvider); +// } + //TODO remove after improved ParameterProvider handling + ParameterProvider stringMessagesParameterProvider = new LocalizationParameterProvider(Locale.ENGLISH, getStringMessages()); + registerParameterProvider(stringMessagesParameterProvider); } @Override From 95ee34331b4b8b682fd662b2106aae745df9abce Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 8 Jan 2015 17:09:24 +0100 Subject: [PATCH 008/264] fixed post-merge problem in PolarDataMiner to make compileable --- .../src/com/sap/sailing/polars/mining/PolarDataMiner.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index f9f9e8f6768..03defdba107 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -33,6 +33,7 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; import com.sap.sailing.polars.mining.IncrementalRegressionProcessor.SpeedWithConfidenceAndDataCount; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.data.ClusterGroup; import com.sap.sse.datamining.functions.Function; From 1cecc06d037c791e9bc2a455c73bf476ef7a4c76 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Mon, 12 Jan 2015 15:38:38 +0100 Subject: [PATCH 009/264] ParameterProviders for localization parameters are automatically resolved --- .../impl/TestDimensionsValuesQuery.java | 20 +++--- .../datamining/impl/TestProcessorQuery.java | 13 ++-- ...llelMultiDimensionalGroupingProcessor.java | 14 ++-- ...TestFunctionManagerAsFunctionProvider.java | 52 --------------- .../TestMethodWrappingFunctionInvocation.java | 9 +-- .../test/util/ComponentTestsUtil.java | 5 +- .../sap/sse/datamining/DataMiningServer.java | 3 - .../ModifiableDataMiningServer.java | 3 - .../factories/ProcessorFactory.java | 42 ++++-------- .../datamining/factories/QueryFactory.java | 65 +++++++++++++------ .../functions/FunctionProvider.java | 2 - .../functions/FunctionRegistry.java | 3 - .../functions/ParameterProvider.java | 2 - .../datamining/impl/DataMiningServerImpl.java | 34 +--------- .../impl/functions/FunctionManager.java | 58 +---------------- .../LocalizationParameterProvider.java | 11 +--- .../impl/functions/NullParameterProvider.java | 3 +- .../functions/SimpleParameterProvider.java | 34 +--------- 18 files changed, 91 insertions(+), 282 deletions(-) diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestDimensionsValuesQuery.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestDimensionsValuesQuery.java index ac9b86fa329..1fb879d5590 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestDimensionsValuesQuery.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestDimensionsValuesQuery.java @@ -14,11 +14,13 @@ import java.util.concurrent.ExecutionException; import org.junit.Before; import org.junit.Test; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.DataRetrieverChainBuilder; import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.Query; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.shared.GroupKey; @@ -71,16 +73,16 @@ public class TestDimensionsValuesQuery { protected Processor, ?> createFirstProcessor() { Processor, Map>> resultCollector = ComponentTestsUtil.getProcessorFactory().createGroupedDataCollectingAsSetProcessor(this); - Collection> legDimensions = new ArrayList<>(); - legDimensions.add(dimensionLegNumber); - legDimensions.add(dimensionCompetitorName); - legDimensions.add(dimensionCompetitorSailID); + Collection, ParameterProvider>> legDimensions = new ArrayList<>(); + legDimensions.add(new Pair<>(dimensionLegNumber, ParameterProvider.NULL)); + legDimensions.add(new Pair<>(dimensionCompetitorName, ParameterProvider.NULL)); + legDimensions.add(new Pair<>(dimensionCompetitorSailID, ParameterProvider.NULL)); - Collection> raceDimensions = new ArrayList<>(); - raceDimensions.add(dimensionRegattaName); - raceDimensions.add(dimensionRaceName); - raceDimensions.add(dimensionBoatClassName); - raceDimensions.add(dimensionYear); + Collection, ParameterProvider>> raceDimensions = new ArrayList<>(); + raceDimensions.add(new Pair<>(dimensionRegattaName, ParameterProvider.NULL)); + raceDimensions.add(new Pair<>(dimensionRaceName, ParameterProvider.NULL)); + raceDimensions.add(new Pair<>(dimensionBoatClassName, ParameterProvider.NULL)); + raceDimensions.add(new Pair<>(dimensionYear, ParameterProvider.NULL)); DataRetrieverChainBuilder> chainBuilder = dataRetrieverChainDefinition.startBuilding(ConcurrencyTestsUtil.getExecutor()); chainBuilder.stepFurther(); //Initialization diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java index 1fe21de993d..a1bb8b28ec1 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/TestProcessorQuery.java @@ -20,20 +20,20 @@ import java.util.concurrent.TimeoutException; import org.junit.Before; import org.junit.Test; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.AdditionalResultDataBuilder; import com.sap.sse.datamining.Query; import com.sap.sse.datamining.components.FilterCriterion; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.factories.ProcessorFactory; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionProvider; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.components.AbstractSimpleParallelProcessor; import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.ParallelFilteringProcessor; import com.sap.sse.datamining.impl.criterias.AbstractFilterCriterion; -import com.sap.sse.datamining.impl.functions.FunctionManager; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.QueryResult; import com.sap.sse.datamining.shared.Unit; @@ -59,8 +59,7 @@ public class TestProcessorQuery { @Before public void initializeProcessorFactory() { - FunctionProvider emptyFunctionProvider = new FunctionManager(); - processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor(), emptyFunctionProvider); + processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor()); } @Test @@ -118,11 +117,11 @@ public class TestProcessorQuery { Method getCrossSumMethod = FunctionTestsUtil.getMethodFromClass(Number.class, "getCrossSum"); Function getCrossSumFunction = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(getCrossSumMethod); - Processor, GroupedDataEntry> crossSumExtractor = processorFactory.createExtractionProcessor(sumAggregator, getCrossSumFunction); + Processor, GroupedDataEntry> crossSumExtractor = processorFactory.createExtractionProcessor(sumAggregator, getCrossSumFunction, ParameterProvider.NULL); - List> dimensions = new ArrayList<>(); + List, ParameterProvider>> dimensions = new ArrayList<>(); Function getLengthFunction = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(FunctionTestsUtil.getMethodFromClass(Number.class, "getLength")); - dimensions.add(getLengthFunction); + dimensions.add(new Pair<>(getLengthFunction, ParameterProvider.NULL)); Processor> lengthGrouper = processorFactory.createGroupingProcessor(Number.class, crossSumExtractor, dimensions); Collection> filtrationResultReceivers = new ArrayList<>(); filtrationResultReceivers.add(lengthGrouper); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java index 5255cbda55f..042dcd07834 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/components/TestParallelMultiDimensionalGroupingProcessor.java @@ -11,9 +11,11 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.factories.ProcessorFactory; import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.functions.MethodWrappingFunction; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.GenericGroupKey; @@ -46,9 +48,9 @@ public class TestParallelMultiDimensionalGroupingProcessor { receivers = new ArrayList<>(); receivers.add(receiver); - List> dimensions = new ArrayList>(); - dimensions.add(new MethodWrappingFunction<>(Number.class.getMethod("getLength", new Class[0]), int.class)); - dimensions.add(new MethodWrappingFunction<>(Number.class.getMethod("getCrossSum", new Class[0]), int.class)); + List, ParameterProvider>> dimensions = new ArrayList<>(); + dimensions.add(new Pair<>(new MethodWrappingFunction<>(Number.class.getMethod("getLength", new Class[0]), int.class), ParameterProvider.NULL)); + dimensions.add(new Pair<>(new MethodWrappingFunction<>(Number.class.getMethod("getCrossSum", new Class[0]), int.class), ParameterProvider.NULL)); processor = ComponentTestsUtil.getProcessorFactory().createGroupingProcessor(Number.class, receivers, dimensions); } @@ -60,15 +62,15 @@ public class TestParallelMultiDimensionalGroupingProcessor { @Test(expected=IllegalArgumentException.class) public void testConstructionWithEmptyDimensions() { - List> dimensions = new ArrayList<>(); + List, ParameterProvider>> dimensions = new ArrayList<>(); processorFactory.createGroupingProcessor(Number.class, receivers, dimensions); } @Test(expected=IllegalArgumentException.class) public void testConstructionWithFunctionsInsteadOfDimensions() { - List> functions = new ArrayList<>(); + List, ParameterProvider>> functions = new ArrayList<>(); Method method = FunctionTestsUtil.getMethodFromSimpleClassWithMarkedMethod("sideEffectFreeValue"); - functions.add(FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(method)); + functions.add(new Pair<>(FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(method), ParameterProvider.NULL)); processorFactory.createGroupingProcessor(Number.class, receivers, functions); } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java index 3dfa5f63a32..72f05612a58 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionProvider.java @@ -5,7 +5,6 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -17,8 +16,6 @@ import org.junit.Test; import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionProvider; -import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.SimpleDataRetrieverChainDefinition; import com.sap.sse.datamining.shared.dto.FunctionDTO; @@ -151,54 +148,5 @@ public class TestFunctionManagerAsFunctionProvider { public void testGetFunctionForNullDTO() { assertThat(functionManager.getFunctionForDTO(null), is(nullValue())); } - - @Test - public void testGetParameterProviderForNullaryFunction() { - Method nullaryDimensionMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "dimension"); - Function nullaryDimension = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(nullaryDimensionMethod); - - FunctionProvider functionProvider = new FunctionManager(); - assertThat(functionProvider.getParameterProviderFor(nullaryDimension), is(ParameterProvider.NULL)); - } - - @Test - public void testGetPrameterProviderForFunctionsWithParameters() { - FunctionManager functionManager = new FunctionManager(); - ParameterProvider intParameterProvider = new ParameterProvider() { - @Override - public Object[] getParameters() { - return new Object[] {1}; - } - - @Override - public Iterable> getParameterTypes() { - Collection> parameterTypes = new ArrayList<>(); - parameterTypes.add(int.class); - return parameterTypes; - } - }; - functionManager.registerParameterProvider(intParameterProvider); - LocalizationParameterProvider stringMessagesParameterProvider = new LocalizationParameterProvider(Locale.ENGLISH, stringMessages); - functionManager.registerParameterProvider(stringMessagesParameterProvider); - - Method incrementMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "increment", int.class); - Function increment = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(incrementMethod); - assertThat(functionManager.getParameterProviderFor(increment), is(intParameterProvider)); - - Method getLocalizedNameMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "getLocalizedName", Locale.class, DataMiningStringMessages.class); - Function getLocalizedName = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(getLocalizedNameMethod); - assertThat(functionManager.getParameterProviderFor(getLocalizedName), is(stringMessagesParameterProvider)); - } - - @Test - public void testGetParameterProviderForFunctionWithoutAnAvailableParameterProvider() { - FunctionManager functionManager = new FunctionManager(); - //Adding a wrong ParameterProvider to increase code coverage - functionManager.registerParameterProvider(new LocalizationParameterProvider(Locale.ENGLISH, stringMessages)); - - Method incrementMethod = FunctionTestsUtil.getMethodFromClass(SimpleClassWithMarkedMethods.class, "increment", int.class); - Function increment = FunctionTestsUtil.getFunctionFactory().createMethodWrappingFunction(incrementMethod); - assertThat(functionManager.getParameterProviderFor(increment), nullValue()); - } } diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java index 6420c1f13ce..2b8d9647f1b 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestMethodWrappingFunctionInvocation.java @@ -4,13 +4,10 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; -import java.util.Collections; - import org.junit.Before; import org.junit.Test; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.test.functions.test_classes.DataTypeWithContext; import com.sap.sse.datamining.test.functions.test_classes.DataTypeWithContextImpl; import com.sap.sse.datamining.test.functions.test_classes.SimpleClassWithMarkedMethods; @@ -37,15 +34,13 @@ public class TestMethodWrappingFunctionInvocation { public void testInvocationWithParameters() { SimpleClassWithMarkedMethods instance = new SimpleClassWithMarkedMethods(); final int valueToIncrement = 10; - ParameterProvider parameterProvider = new SimpleParameterProvider(Collections.emptyList(), new Object[] {valueToIncrement}); - assertThat(increment.tryToInvoke(instance, parameterProvider), is(instance.increment(valueToIncrement))); + assertThat(increment.tryToInvoke(instance, () -> {return new Object[] {valueToIncrement};} ), is(instance.increment(valueToIncrement))); } @Test public void testInvocationWithWrongParameters() { DataTypeWithContext dataEntry = new DataTypeWithContextImpl("Regatta Name", "Race Name", 7); - ParameterProvider parameterProvider = new SimpleParameterProvider(Collections.emptyList(), new Object[] {"Wrong Parameter"}); - assertThat(getRegattaName.tryToInvoke(dataEntry, parameterProvider), is(nullValue())); + assertThat(getRegattaName.tryToInvoke(dataEntry, () -> {return new Object[] {"Wrong Parameter"};} ), is(nullValue())); SimpleClassWithMarkedMethods instance = new SimpleClassWithMarkedMethods(); assertThat(increment.tryToInvoke(instance), is(nullValue())); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java index ff115301ad3..fbc7167a21f 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/ComponentTestsUtil.java @@ -5,8 +5,6 @@ import java.util.Arrays; import java.util.Collection; import com.sap.sse.datamining.factories.ProcessorFactory; -import com.sap.sse.datamining.functions.FunctionProvider; -import com.sap.sse.datamining.impl.functions.FunctionManager; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_Boat; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_BoatClass; import com.sap.sse.datamining.test.functions.registry.test_classes.Test_Competitor; @@ -24,8 +22,7 @@ import com.sap.sse.datamining.test.functions.registry.test_classes.impl.Test_Tea public final class ComponentTestsUtil { - private final static FunctionProvider emptyFunctionProvider = new FunctionManager(); - private final static ProcessorFactory processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor(), emptyFunctionProvider); + private final static ProcessorFactory processorFactory = new ProcessorFactory(ConcurrencyTestsUtil.getExecutor()); public static ProcessorFactory getProcessorFactory() { return processorFactory; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataMiningServer.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataMiningServer.java index 76fe4583f0a..e8a3102a089 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataMiningServer.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataMiningServer.java @@ -7,7 +7,6 @@ import java.util.concurrent.ExecutorService; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionProvider; -import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.DataRetrieverChainDefinitionProvider; import com.sap.sse.datamining.shared.QueryDefinitionDTO; @@ -35,8 +34,6 @@ public interface DataMiningServer { * @return The first function, that matches the given DTO or null */ public Function getFunctionForDTO(FunctionDTO functionDTO); - - public ParameterProvider getParameterProviderFor(Function function); public DataRetrieverChainDefinitionProvider getDataRetrieverChainDefinitionProvider(); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/ModifiableDataMiningServer.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/ModifiableDataMiningServer.java index d8e8f6cc250..a2448222b25 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/ModifiableDataMiningServer.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/ModifiableDataMiningServer.java @@ -1,7 +1,6 @@ package com.sap.sse.datamining; import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; public interface ModifiableDataMiningServer extends DataMiningServer { @@ -13,10 +12,8 @@ public interface ModifiableDataMiningServer extends DataMiningServer { public void registerAllWithInternalFunctionPolicy(Iterable> classesToScan); public void registerAllWithExternalFunctionPolicy(Iterable> externalClassesToScan); - public void registerParameterProvider(ParameterProvider parameterProvider); public void unregisterAllFunctionsOf(Iterable> classesToUnregister); - public void unregisterParameterProvider(ParameterProvider parameterProvider); public void registerDataRetrieverChainDefinition(DataRetrieverChainDefinition dataRetrieverChainDefinition); public void unregisterDataRetrieverChainDefinition(DataRetrieverChainDefinition dataRetrieverChainDefinition); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java index edbe5ce44f1..6f66b587061 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/ProcessorFactory.java @@ -11,7 +11,6 @@ import java.util.concurrent.ExecutorService; import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionProvider; import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.ProcessorQuery; @@ -29,11 +28,9 @@ import com.sap.sse.datamining.shared.components.AggregatorType; public class ProcessorFactory { private final ExecutorService executor; - private final FunctionProvider functionProvider; - public ProcessorFactory(ExecutorService executor, FunctionProvider functionProvider) { + public ProcessorFactory(ExecutorService executor) { this.executor = executor; - this.functionProvider = functionProvider; } /** @@ -80,16 +77,14 @@ public class ProcessorFactory { * Creates an extraction processor with the given aggregation processor as result receiver. */ public Processor, GroupedDataEntry> createExtractionProcessor( - Processor, ?> aggregationProcessor, Function extractionFunction) { + Processor, ?> aggregationProcessor, Function extractionFunction, ParameterProvider parameterProvider) { Collection, ?>> resultReceivers = new ArrayList<>(); resultReceivers.add(aggregationProcessor); - return createExtractionProcessor(resultReceivers, extractionFunction); + return createExtractionProcessor(resultReceivers, extractionFunction, parameterProvider); } public Processor, GroupedDataEntry> createExtractionProcessor( - Collection, ?>> resultReceivers, Function extractionFunction) { - ParameterProvider parameterProvider = functionProvider.getParameterProviderFor(extractionFunction); - throwExceptionIfParameterProviderIsNull(extractionFunction, parameterProvider); + Collection, ?>> resultReceivers, Function extractionFunction, ParameterProvider parameterProvider) { return new ParallelGroupedElementsValueExtractionProcessor(executor, resultReceivers, extractionFunction, parameterProvider); } @@ -97,27 +92,14 @@ public class ProcessorFactory { /** * Creates a grouping processor with the given extraction processor as result receiver. */ - public Processor> createGroupingProcessor(Class dataType, Processor, ?> extractionProcessor, List> dimensionsToGroupBy) { + public Processor> createGroupingProcessor(Class dataType, Processor, ?> extractionProcessor, Iterable, ParameterProvider>> dimensionsToGroupByWithParameterProvider) { Collection, ?>> resultReceivers = new ArrayList<>(); resultReceivers.add(extractionProcessor); - return createGroupingProcessor(dataType, resultReceivers, dimensionsToGroupBy); + return createGroupingProcessor(dataType, resultReceivers, dimensionsToGroupByWithParameterProvider); } - public Processor> createGroupingProcessor(Class dataType, Collection, ?>> resultReceivers, List> dimensionsToGroupBy) { - Collection, ParameterProvider>> dimensionsToGroupByWithNullParameterProvider = new ArrayList<>(); - for (Function dimension : dimensionsToGroupBy) { - ParameterProvider parameterProvider = functionProvider.getParameterProviderFor(dimension); - throwExceptionIfParameterProviderIsNull(dimension, parameterProvider); - dimensionsToGroupByWithNullParameterProvider.add(new Pair, ParameterProvider>(dimension, parameterProvider)); - } - return new ParallelMultiDimensionsValueNestingGroupingProcessor<>(dataType, executor, resultReceivers, dimensionsToGroupByWithNullParameterProvider); - } - - private void throwExceptionIfParameterProviderIsNull(Function function, ParameterProvider parameterProvider) { - if (parameterProvider == null) { - throw new NullPointerException("There is no " + ParameterProvider.class.getSimpleName() + " available for the " + - Function.class.getSimpleName() + " '" + function.toString() + "'"); - } + public Processor> createGroupingProcessor(Class dataType, Collection, ?>> resultReceivers, Iterable, ParameterProvider>> dimensionsToGroupByWithParameterProvider) { + return new ParallelMultiDimensionsValueNestingGroupingProcessor<>(dataType, executor, resultReceivers, dimensionsToGroupByWithParameterProvider); } public Processor> createGroupingProcessorWithParameter(Class dataType, Collection, ?>> resultReceivers, List, ParameterProvider>> dimensionsToGroupByWithParameterProvider) { @@ -129,18 +111,18 @@ public class ProcessorFactory { */ @SuppressWarnings("unchecked") public Collection>> createGroupingExtractorsForDimensions(Class dataType, - Processor, ?> valueCollector, Iterable> dimensions, + Processor, ?> valueCollector, Iterable, ParameterProvider>> dimensionsToGroupByWithParameterProvider, DataMiningStringMessages stringMessages, Locale locale) { Collection, ?>> extractionResultReceivers = new ArrayList<>(); extractionResultReceivers.add(valueCollector); Collection>> groupingExtractors = new ArrayList<>(); - for (Function dimension : dimensions) { - Processor, GroupedDataEntry> dimensionValueExtractor = createExtractionProcessor(extractionResultReceivers, (Function) dimension); + for (Pair, ParameterProvider> dimensionWithParameterProvider : dimensionsToGroupByWithParameterProvider) { + Processor, GroupedDataEntry> dimensionValueExtractor = createExtractionProcessor(extractionResultReceivers, (Function) dimensionWithParameterProvider.getA(), dimensionWithParameterProvider.getB()); Collection, ?>> groupingResultReceivers = new ArrayList<>(); groupingResultReceivers.add(dimensionValueExtractor); - Processor> byDimensionGrouper = new ParallelByDimensionGroupingProcessor<>(dataType, executor, groupingResultReceivers, dimension, stringMessages, locale); + Processor> byDimensionGrouper = new ParallelByDimensionGroupingProcessor<>(dataType, executor, groupingResultReceivers, dimensionWithParameterProvider.getA(), stringMessages, locale); groupingExtractors.add(byDimensionGrouper); } return groupingExtractors; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java index 8e17ab947fd..371e7cace02 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/factories/QueryFactory.java @@ -3,12 +3,14 @@ package com.sap.sse.datamining.factories; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutorService; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.DataRetrieverChainBuilder; import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.Query; @@ -16,7 +18,6 @@ import com.sap.sse.datamining.QueryDefinition; import com.sap.sse.datamining.components.FilterCriterion; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionProvider; import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; import com.sap.sse.datamining.impl.ProcessorQuery; @@ -24,27 +25,28 @@ import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.criterias.AndCompoundFilterCriterion; import com.sap.sse.datamining.impl.criterias.CompoundFilterCriterion; import com.sap.sse.datamining.impl.criterias.FunctionValuesFilterCriterion; +import com.sap.sse.datamining.impl.functions.LocalizationParameterProvider; import com.sap.sse.datamining.shared.GroupKey; public class QueryFactory { public Query createQuery(DataSourceType dataSource, QueryDefinition queryDefinition, - DataMiningStringMessages stringMessages, ExecutorService executor, FunctionProvider functionProvider) { + DataMiningStringMessages stringMessages, ExecutorService executor) { return new ProcessorQuery(dataSource, stringMessages, queryDefinition.getLocale()) { @Override protected Processor createFirstProcessor() { - ProcessorFactory processorFactory = new ProcessorFactory(executor, functionProvider); + ProcessorFactory processorFactory = new ProcessorFactory(executor); Function extractionFunction = queryDefinition.getStatisticToCalculate(); Class dataTypeToRetrieve = queryDefinition.getDataType(); Processor, Map> aggregationProcessor = processorFactory.createAggregationProcessor(/*query*/ this, queryDefinition.getAggregatorType(), queryDefinition.getResultType()); - Processor, GroupedDataEntry> extractionProcessor = processorFactory.createExtractionProcessor(aggregationProcessor, extractionFunction); + Processor, GroupedDataEntry> extractionProcessor = processorFactory.createExtractionProcessor(aggregationProcessor, extractionFunction, getParameterProviderFor(extractionFunction, stringMessages, queryDefinition.getLocale())); - Processor> groupingProcessor = processorFactory.createGroupingProcessor(dataTypeToRetrieve, extractionProcessor, queryDefinition.getDimensionsToGroupBy()); + Processor> groupingProcessor = processorFactory.createGroupingProcessor(dataTypeToRetrieve, extractionProcessor, getParameterProvidersFor(queryDefinition.getDimensionsToGroupBy(), stringMessages, queryDefinition.getLocale())); DataRetrieverChainBuilder chainBuilder = queryDefinition.getDataRetrieverChainDefinition().startBuilding(executor); - Map> criteriaMappedByRetrieverLevel = createFilterCriteria(queryDefinition.getFilterSelection(), functionProvider); + Map> criteriaMappedByRetrieverLevel = createFilterCriteria(queryDefinition.getFilterSelection()); while (chainBuilder.canStepFurther()) { chainBuilder.stepFurther(); @@ -60,14 +62,45 @@ public class QueryFactory { }; } + private Iterable, ParameterProvider>> getParameterProvidersFor(Iterable> functions, DataMiningStringMessages stringMessages, Locale locale) { + Collection, ParameterProvider>> functionsWithParameterProvider = new ArrayList<>(); + for (Function function : functions) { + functionsWithParameterProvider.add(new Pair<>(function, getParameterProviderFor(function, stringMessages, locale))); + } + return functionsWithParameterProvider; + } + + private ParameterProvider getParameterProviderFor(Function function, DataMiningStringMessages stringMessages, Locale locale) { + if (functionNeedsLocalizationParameters(function)) { + return new LocalizationParameterProvider(locale, stringMessages); + } + return ParameterProvider.NULL; + } + + private boolean functionNeedsLocalizationParameters(Function function) { + Iterator> parameterTypesIterator = function.getParameters().iterator(); + int parameterCount = 0; + while (parameterTypesIterator.hasNext()) { + parameterCount++; + parameterTypesIterator.next(); + } + if (parameterCount != 2) { + return false; + } + + parameterTypesIterator = function.getParameters().iterator(); + Class firstParameter = parameterTypesIterator.next(); + Class secondParameter = parameterTypesIterator.next(); + return firstParameter.isAssignableFrom(Locale.class) && secondParameter.isAssignableFrom(DataMiningStringMessages.class); + } + @SuppressWarnings("unchecked") - private Map> createFilterCriteria(Map, Collection>> filterSelection, FunctionProvider functionProvider) { + private Map> createFilterCriteria(Map, Collection>> filterSelection) { Map> criteriaMappedByRetrieverLevel = new HashMap<>(); for (Entry, Collection>> levelFilterSelection : filterSelection.entrySet()) { for (Entry, Collection> levelFilterSelectionEntry : levelFilterSelection.getValue().entrySet()) { Function function = levelFilterSelectionEntry.getKey(); - ParameterProvider parameterProvider = functionProvider.getParameterProviderFor(function); - throwExceptionIfParameterProviderIsNull(function, parameterProvider); + ParameterProvider parameterProvider = ParameterProvider.NULL; Class dataType = (Class) function.getDeclaringType(); if (!criteriaMappedByRetrieverLevel.containsKey(levelFilterSelection.getKey())) { @@ -80,23 +113,15 @@ public class QueryFactory { } return (Map>)(Map) criteriaMappedByRetrieverLevel; } - - private void throwExceptionIfParameterProviderIsNull(Function function, ParameterProvider parameterProvider) { - if (parameterProvider == null) { - throw new NullPointerException("There is no " + ParameterProvider.class.getSimpleName() + " available for the " + - Function.class.getSimpleName() + " '" + function.toString() + "'"); - } - } public Query> createDimensionValuesQuery(DataSource dataSource, DataRetrieverChainDefinition dataRetrieverChainDefinition, int retrieverLevel, Iterable> dimensions, Locale locale, - DataMiningStringMessages stringMessages, ExecutorService executor, - FunctionProvider functionProvider) { + DataMiningStringMessages stringMessages, ExecutorService executor) { return new ProcessorQuery, DataSource>(dataSource, stringMessages, locale) { @Override protected Processor createFirstProcessor() { - ProcessorFactory processorFactory = new ProcessorFactory(executor, functionProvider); + ProcessorFactory processorFactory = new ProcessorFactory(executor); Processor, Map>> valueCollector = processorFactory.createGroupedDataCollectingAsSetProcessor(/*query*/ this); @@ -106,7 +131,7 @@ public class QueryFactory { chainBuilder.stepFurther(); } for (Processor resultReceiver : processorFactory.createGroupingExtractorsForDimensions( - chainBuilder.getCurrentRetrievedDataType(), valueCollector, dimensions, stringMessages, locale)) { + chainBuilder.getCurrentRetrievedDataType(), valueCollector, getParameterProvidersFor(dimensions, stringMessages, locale), stringMessages, locale)) { chainBuilder.addResultReceiver(resultReceiver); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java index e40e3176a04..6a8c3f450b9 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java @@ -21,7 +21,5 @@ public interface FunctionProvider { * @return The first function, that matches the given DTO or null */ public Function getFunctionForDTO(FunctionDTO functionDTO); - - public ParameterProvider getParameterProviderFor(Function function); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java index ddb4751863b..4f0a06a33ec 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java @@ -6,10 +6,8 @@ public interface FunctionRegistry { public void registerAllWithInternalFunctionPolicy(Iterable> classesToScan); public void registerAllWithExternalFunctionPolicy(Iterable> externalClassesToScan); - public void registerParameterProvider(ParameterProvider parameterProvider); public void unregisterAllFunctionsOf(Iterable> classesToUnregister); - public void unregisterParameterProvider(ParameterProvider parameterProvider); public Collection> getAllFunctions(); public Collection> getAllFunctionsOf(Class declaringType); @@ -19,6 +17,5 @@ public interface FunctionRegistry { public Collection> getDimensionsOf(Class declaringType); public Collection> getExternalFunctions(); public Collection> getExternalFunctionsOf(Class declaringType); - public Iterable getAllParameterProviders(); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java index d17f511e5d6..7505873159a 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/ParameterProvider.java @@ -6,8 +6,6 @@ public interface ParameterProvider { public static final ParameterProvider NULL = new NullParameterProvider(); - public Iterable> getParameterTypes(); - public Object[] getParameters(); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java index 2a29f3e4f33..34a051df5df 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataMiningServerImpl.java @@ -17,9 +17,7 @@ import com.sap.sse.datamining.factories.QueryFactory; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionProvider; import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.i18n.DataMiningStringMessages; -import com.sap.sse.datamining.impl.functions.LocalizationParameterProvider; import com.sap.sse.datamining.impl.i18n.CompoundDataMiningStringMessages; import com.sap.sse.datamining.shared.QueryDefinitionDTO; import com.sap.sse.datamining.shared.dto.FunctionDTO; @@ -43,19 +41,6 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { this.functionRegistry = functionRegistry; this.functionProvider = functionProvider; this.dataRetrieverChainDefinitionRegistry = dataRetrieverChainDefinitionRegistry; - - initializeStringMessagesParameterProvider(); - } - - private void initializeStringMessagesParameterProvider() { - //TODO enable after improved ParameterProvider handling -// for (Locale locale : DataMiningStringMessages.Util.getSupportedLocales()) { -// ParameterProvider stringMessagesParameterProvider = new LocalizationParameterProvider(locale, getStringMessages()); -// registerParameterProvider(stringMessagesParameterProvider); -// } - //TODO remove after improved ParameterProvider handling - ParameterProvider stringMessagesParameterProvider = new LocalizationParameterProvider(Locale.ENGLISH, getStringMessages()); - registerParameterProvider(stringMessagesParameterProvider); } @Override @@ -93,20 +78,10 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { functionRegistry.registerAllWithExternalFunctionPolicy(externalClassesToScan); } - @Override - public void registerParameterProvider(ParameterProvider parameterProvider) { - functionRegistry.registerParameterProvider(parameterProvider); - } - @Override public void unregisterAllFunctionsOf(Iterable> classesToUnregister) { functionRegistry.unregisterAllFunctionsOf(classesToUnregister); } - - @Override - public void unregisterParameterProvider(ParameterProvider parameterProvider) { - functionRegistry.unregisterParameterProvider(parameterProvider); - } @Override public FunctionProvider getFunctionProvider() { @@ -143,11 +118,6 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { return functionProvider.getFunctionForDTO(functionDTO); } - @Override - public ParameterProvider getParameterProviderFor(Function function) { - return functionProvider.getParameterProviderFor(function); - } - @Override public DataRetrieverChainDefinitionProvider getDataRetrieverChainDefinitionProvider() { return dataRetrieverChainDefinitionRegistry; @@ -215,14 +185,14 @@ public class DataMiningServerImpl implements ModifiableDataMiningServer { @Override public Query createQuery(DataSource dataSource, QueryDefinition queryDefinition) { - return queryFactory.createQuery(dataSource, queryDefinition, getStringMessages(), getExecutorService(), getFunctionProvider()); + return queryFactory.createQuery(dataSource, queryDefinition, getStringMessages(), getExecutorService()); } @Override public Query> createDimensionValuesQuery(DataSource dataSource, DataRetrieverChainDefinition dataRetrieverChainDefinition, int retrieverLevel, Iterable> dimensions, Locale locale) { - return queryFactory.createDimensionValuesQuery(dataSource, dataRetrieverChainDefinition, retrieverLevel, dimensions, locale, getStringMessages(), getExecutorService(), getFunctionProvider()); + return queryFactory.createDimensionValuesQuery(dataSource, dataRetrieverChainDefinition, retrieverLevel, dimensions, locale, getStringMessages(), getExecutorService()); } } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java index bda7b355749..946f3a2a350 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java @@ -6,11 +6,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -21,7 +20,6 @@ import com.sap.sse.datamining.factories.FunctionFactory; import com.sap.sse.datamining.functions.Function; import com.sap.sse.datamining.functions.FunctionProvider; import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.functions.ParameterProvider; import com.sap.sse.datamining.impl.DataRetrieverTypeWithInformation; import com.sap.sse.datamining.impl.functions.criterias.FunctionMatchesDTOFilterCriterion; import com.sap.sse.datamining.impl.functions.criterias.MethodIsValidConnectorFilterCriterion; @@ -71,8 +69,6 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { private final Map, Set>> externalFunctions; private final Collection, Set>>> functionMaps; - - private final Collection parameterProviders; public FunctionManager() { functionDTOFactory = new FunctionDTOFactory(); @@ -86,8 +82,6 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { functionMaps.add(statistics); functionMaps.add(dimensions); functionMaps.add(externalFunctions); - - parameterProviders = new HashSet<>(); } @Override @@ -170,11 +164,6 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { } externalFunctions.get(declaringType).add(function); } - - @Override - public void registerParameterProvider(ParameterProvider parameterProvider) { - parameterProviders.add(parameterProvider); - } @Override public void unregisterAllFunctionsOf(Iterable> classesToUnregister) { @@ -188,11 +177,6 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { functionMap.remove(classToUnregister); } } - - @Override - public void unregisterParameterProvider(ParameterProvider parameterProvider) { - parameterProviders.remove(parameterProvider); - } @Override public Collection> getAllFunctions() { @@ -253,11 +237,6 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { return set; } - @Override - public Iterable getAllParameterProviders() { - return parameterProviders; - } - @Override public Collection> getAllStatistics() { return getStatistics(); @@ -381,40 +360,5 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { private Function getFunctionToReturn(Collection> functionsMatchingDTO) { return functionsMatchingDTO.isEmpty() ? null : functionsMatchingDTO.iterator().next(); } - - @Override - public ParameterProvider getParameterProviderFor(Function function) { - Iterable> functionParameterTypes = function.getParameters(); - if (!functionParameterTypes.iterator().hasNext()) { - return ParameterProvider.NULL; - } - - for (ParameterProvider parameterProvider : getAllParameterProviders()) { - if (parametersAreMatching(function, parameterProvider)) { - return parameterProvider; - } - } - - return null; - } - - private boolean parametersAreMatching(Function function, ParameterProvider parameterProvider) { - Iterator> functionParametersIterator = function.getParameters().iterator(); - Iterator> parameterProviderIterator = parameterProvider.getParameterTypes().iterator(); - - while (functionParametersIterator.hasNext() && parameterProviderIterator.hasNext()) { - Class functionParameter = functionParametersIterator.next(); - Class parameterProviderParameter = parameterProviderIterator.next(); - if (!functionParameter.isAssignableFrom(parameterProviderParameter)) { - return false; - } - } - - if (functionParametersIterator.hasNext() || parameterProviderIterator.hasNext()) { - return false; - } - - return true; - } } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java index 846f1e2d030..aefc59e12d7 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/LocalizationParameterProvider.java @@ -1,7 +1,5 @@ package com.sap.sse.datamining.impl.functions; -import java.util.ArrayList; -import java.util.Collection; import java.util.Locale; import com.sap.sse.datamining.i18n.DataMiningStringMessages; @@ -9,14 +7,7 @@ import com.sap.sse.datamining.i18n.DataMiningStringMessages; public class LocalizationParameterProvider extends SimpleParameterProvider { public LocalizationParameterProvider(Locale locale, DataMiningStringMessages stringMessages) { - super(createParameterTypes(), new Object[] {locale, stringMessages}); - } - - private static Iterable> createParameterTypes() { - Collection> parameterTypes = new ArrayList<>(); - parameterTypes.add(Locale.class); - parameterTypes.add(DataMiningStringMessages.class); - return parameterTypes; + super(new Object[] {locale, stringMessages}); } } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/NullParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/NullParameterProvider.java index 4dcb9912292..83a5511989a 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/NullParameterProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/NullParameterProvider.java @@ -1,11 +1,10 @@ package com.sap.sse.datamining.impl.functions; -import java.util.Collections; public class NullParameterProvider extends SimpleParameterProvider { public NullParameterProvider() { - super(Collections.emptyList(), new Object[0]); + super(new Object[0]); } } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java index c3b83f0e1a0..0717107c18c 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/SimpleParameterProvider.java @@ -4,47 +4,15 @@ import com.sap.sse.datamining.functions.ParameterProvider; public class SimpleParameterProvider implements ParameterProvider { - private final Iterable> parameterTypes; private final Object[] parameters; - public SimpleParameterProvider(Iterable> parameterTypes, Object[] parameters) { - this.parameterTypes = parameterTypes; + public SimpleParameterProvider(Object[] parameters) { this.parameters = parameters; } - @Override - public Iterable> getParameterTypes() { - return parameterTypes; - } - @Override public Object[] getParameters() { return parameters; } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((parameterTypes == null) ? 0 : parameterTypes.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SimpleParameterProvider other = (SimpleParameterProvider) obj; - if (parameterTypes == null) { - if (other.parameterTypes != null) - return false; - } else if (!parameterTypes.equals(other.parameterTypes)) - return false; - return true; - } } From 844515b3ed12b0938e7e53fdf56e00b8322e7072 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Tue, 13 Jan 2015 13:17:37 +0100 Subject: [PATCH 010/264] Cleaned the FunctionRegistry, FunctionProvider and FunctionManager --- .../TestSailingFunctionsRegistration.java | 9 ++- .../test/util/OpenFunctionManager.java | 18 +++++ ...TestFunctionManagerAsFunctionRegistry.java | 8 +-- .../test/util/OpenFunctionManager.java | 18 +++++ .../functions/FunctionProvider.java | 4 +- .../functions/FunctionRegistry.java | 11 --- .../impl/functions/FunctionManager.java | 72 +++++++------------ 7 files changed, 70 insertions(+), 70 deletions(-) create mode 100644 java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/util/OpenFunctionManager.java create mode 100644 java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/OpenFunctionManager.java diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java index cd8a44cbace..34bb6f7f1ca 100644 --- a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/functions/TestSailingFunctionsRegistration.java @@ -15,6 +15,7 @@ import com.sap.sailing.datamining.Activator; import com.sap.sailing.datamining.data.HasGPSFixContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.test.util.OpenFunctionManager; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Moving; import com.sap.sailing.domain.base.Nationality; @@ -23,18 +24,16 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sse.common.Named; import com.sap.sse.datamining.factories.FunctionFactory; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionRegistry; -import com.sap.sse.datamining.impl.functions.FunctionManager; public class TestSailingFunctionsRegistration { private static FunctionFactory functionFactory = new FunctionFactory(); - private FunctionRegistry functionRegistry; + private OpenFunctionManager functionRegistry; @Before public void setUpFunctionRegistryAndProvider() { - functionRegistry = new FunctionManager(); + functionRegistry = new OpenFunctionManager(); functionRegistry.registerAllWithInternalFunctionPolicy(Activator.getDefault().getInternalClassesWithMarkedMethods()); } @@ -76,7 +75,7 @@ public class TestSailingFunctionsRegistration { @Test public void testImportantRegisteredStatistics() throws NoSuchMethodException, SecurityException { Collection> expectedStatistics = createExpectedImportantRegisteredStatistics(); - Collection> providedStatistics = functionRegistry.getStatistics(); + Collection> providedStatistics = functionRegistry.getAllStatistics(); for (Function statistic : expectedStatistics) { assertThat("The expected statistic '" + statistic + "' wasn't provided.", providedStatistics.contains(statistic), is(true)); diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/util/OpenFunctionManager.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/util/OpenFunctionManager.java new file mode 100644 index 00000000000..137c69bfb3c --- /dev/null +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/test/util/OpenFunctionManager.java @@ -0,0 +1,18 @@ +package com.sap.sailing.datamining.test.util; + +import java.util.Collection; + +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.impl.functions.FunctionManager; + +public class OpenFunctionManager extends FunctionManager { + + public Collection> getDimensions() { + return asSet(dimensions); + } + + public Collection> getExternalFunctions() { + return asSet(externalFunctions); + } + +} diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java index 21f3891c5f9..2366eb4935f 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/functions/TestFunctionManagerAsFunctionRegistry.java @@ -12,12 +12,12 @@ import org.junit.BeforeClass; import org.junit.Test; import com.sap.sse.datamining.functions.Function; -import com.sap.sse.datamining.functions.FunctionRegistry; import com.sap.sse.datamining.test.functions.registry.test_contexts.Test_HasContextWithDeadConnectorEnd; import com.sap.sse.datamining.test.functions.registry.test_contexts.Test_HasLegOfCompetitorContext; import com.sap.sse.datamining.test.functions.registry.test_contexts.Test_HasRaceContext; import com.sap.sse.datamining.test.functions.test_classes.Test_ExternalLibraryClass; import com.sap.sse.datamining.test.util.ExpectedFunctionRegistryUtil; +import com.sap.sse.datamining.test.util.OpenFunctionManager; public class TestFunctionManagerAsFunctionRegistry { @@ -45,7 +45,7 @@ public class TestFunctionManagerAsFunctionRegistry { @Test public void testRegistration() throws NoSuchMethodException, SecurityException { - FunctionRegistry registry = new FunctionManager(); + OpenFunctionManager registry = new OpenFunctionManager(); registry.registerAllWithInternalFunctionPolicy(internalClassesToScan); registry.registerAllWithExternalFunctionPolicy(externalClassesToScan); @@ -55,7 +55,7 @@ public class TestFunctionManagerAsFunctionRegistry { assertThat(registry.getDimensions(), is(expectedDimensions)); Collection> expectedStatistics = expectedFunctionRegistryUtil.getExpectedStatisticsFor(Test_HasLegOfCompetitorContext.class); - assertThat(registry.getStatistics(), is(expectedStatistics)); + assertThat(registry.getAllStatistics(), is(expectedStatistics)); Collection> expectedExternalFunctions = expectedFunctionRegistryUtil.getExpectedExternalFunctionsFor(Test_ExternalLibraryClass.class); assertThat(registry.getExternalFunctions(), is(expectedExternalFunctions)); @@ -63,7 +63,7 @@ public class TestFunctionManagerAsFunctionRegistry { @Test public void testUnregistration() { - FunctionRegistry registry = new FunctionManager(); + OpenFunctionManager registry = new OpenFunctionManager(); registry.registerAllWithInternalFunctionPolicy(internalClassesToScan); Collection> expectedDimensions = new HashSet<>(); diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/OpenFunctionManager.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/OpenFunctionManager.java new file mode 100644 index 00000000000..1f0070ce588 --- /dev/null +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/test/util/OpenFunctionManager.java @@ -0,0 +1,18 @@ +package com.sap.sse.datamining.test.util; + +import java.util.Collection; + +import com.sap.sse.datamining.functions.Function; +import com.sap.sse.datamining.impl.functions.FunctionManager; + +public class OpenFunctionManager extends FunctionManager { + + public Collection> getDimensions() { + return asSet(dimensions); + } + + public Collection> getExternalFunctions() { + return asSet(externalFunctions); + } + +} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java index 6a8c3f450b9..1a57fada325 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionProvider.java @@ -6,9 +6,9 @@ import com.sap.sse.datamining.DataRetrieverChainDefinition; import com.sap.sse.datamining.shared.dto.FunctionDTO; public interface FunctionProvider { - - public Collection> getAllStatistics(); + public Collection> getAllStatistics(); + public Collection> getFunctionsFor(Class sourceType); public Collection> getStatisticsFor(Class sourceType); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java index 4f0a06a33ec..cc2f40446cd 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/functions/FunctionRegistry.java @@ -1,7 +1,5 @@ package com.sap.sse.datamining.functions; -import java.util.Collection; - public interface FunctionRegistry { public void registerAllWithInternalFunctionPolicy(Iterable> classesToScan); @@ -9,13 +7,4 @@ public interface FunctionRegistry { public void unregisterAllFunctionsOf(Iterable> classesToUnregister); - public Collection> getAllFunctions(); - public Collection> getAllFunctionsOf(Class declaringType); - public Collection> getStatistics(); - public Collection> getStatisticsOf(Class declaringType); - public Collection> getDimensions(); - public Collection> getDimensionsOf(Class declaringType); - public Collection> getExternalFunctions(); - public Collection> getExternalFunctionsOf(Class declaringType); - } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java index 946f3a2a350..63aedf5c8ea 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/functions/FunctionManager.java @@ -33,24 +33,24 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { private enum FunctionRetrievalStrategies { All { @Override - public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { - return registry.getAllFunctionsOf(declaringType); + public Collection> retrieveFunctions(Class declaringType, FunctionManager manager) { + return manager.getAllFunctionsOf(declaringType); } }, Dimensions { @Override - public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { - return registry.getDimensionsOf(declaringType); + public Collection> retrieveFunctions(Class declaringType, FunctionManager manager) { + return manager.getDimensionsOf(declaringType); } }, Statistics { @Override - public Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry) { - return registry.getStatisticsOf(declaringType); + public Collection> retrieveFunctions(Class declaringType, FunctionManager manager) { + return manager.getStatisticsOf(declaringType); } }; - public abstract Collection> retrieveFunctions(Class declaringType, FunctionRegistry registry); + public abstract Collection> retrieveFunctions(Class declaringType, FunctionManager manager); } @@ -64,9 +64,9 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { private final FunctionFactory functionFactory; private final FunctionDTOFactory functionDTOFactory; - private final Map, Set>> statistics; - private final Map, Set>> dimensions; - private final Map, Set>> externalFunctions; + protected final Map, Set>> statistics; + protected final Map, Set>> dimensions; + protected final Map, Set>> externalFunctions; private final Collection, Set>>> functionMaps; @@ -178,17 +178,7 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { } } - @Override - public Collection> getAllFunctions() { - Collection> allFunctions = new HashSet<>(); - for (Map, Set>> functionMap : functionMaps) { - allFunctions.addAll(asSet(functionMap)); - } - return allFunctions; - } - - @Override - public Collection> getAllFunctionsOf(Class declaringType) { + private Collection> getAllFunctionsOf(Class declaringType) { Collection> allFunctions = new HashSet<>(); for (Map, Set>> functionMap : functionMaps) { Collection> functions = functionMap.get(declaringType); @@ -198,49 +188,27 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { } return allFunctions; } - + @Override - public Collection> getStatistics() { + public Collection> getAllStatistics() { return asSet(statistics); } - @Override - public Collection> getStatisticsOf(Class declaringType) { + private Collection> getStatisticsOf(Class declaringType) { return statistics.get(declaringType); } - @Override - public Collection> getDimensions() { - return asSet(dimensions); - } - - @Override - public Collection> getDimensionsOf(Class declaringType) { + private Collection> getDimensionsOf(Class declaringType) { return dimensions.get(declaringType); } - @Override - public Collection> getExternalFunctions() { - return asSet(externalFunctions); - } - - @Override - public Collection> getExternalFunctionsOf(Class declaringType) { - return externalFunctions.get(declaringType); - } - - private Collection> asSet(Map>> map) { + protected Collection> asSet(Map>> map) { Collection> set = new HashSet<>(); for (Entry>> entry : map.entrySet()) { set.addAll(entry.getValue()); } return set; } - - @Override - public Collection> getAllStatistics() { - return getStatistics(); - } @Override public Collection> getFunctionsFor(Class sourceType) { @@ -346,6 +314,14 @@ public class FunctionManager implements FunctionRegistry, FunctionProvider { return functionsMatchingDTO; } + private Collection> getAllFunctions() { + Collection> allFunctions = new HashSet<>(); + for (Map, Set>> functionMap : functionMaps) { + allFunctions.addAll(asSet(functionMap)); + } + return allFunctions; + } + private boolean moreThanOneFunctionMatchedDTO(Collection> functionsMatchingDTO) { return functionsMatchingDTO.size() > 1; } From ba06f8f577dc96cbd14a6449fd9f893f46a853ae Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 21 Jan 2015 10:36:58 +0100 Subject: [PATCH 011/264] Added interface for multivariate online regression --- .../OnlineMultiVariateRegression.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java new file mode 100644 index 00000000000..822a0229505 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java @@ -0,0 +1,26 @@ +package com.sap.sailing.polars.regression; + +public interface OnlineMultiVariateRegression { + + /** + * Adds data to the regression. This will change the weights. + * + * @param y + * @param x + * length of the array needs to be the dimension of the regression (depending on the implementation, the + * dimension could be configured in the constructor), else IllegalArgumentsException will be thrown + */ + void addData(double y, double[] x); + + /** + * After adding some data with {@link #addData(double, double[])} this method can be used to estimate y for any + * x-vector. + * + * @param x + * length of the array needs to be the dimension of the regression (depending on the implementation, the + * dimension could be configured in the constructor), else IllegalArgumentsException will be thrown + * @return + */ + double estimateY(double[] x); + +} From 4e207c872d5a37bb1c2efc8d3e591113260f438c Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 21 Jan 2015 11:05:19 +0100 Subject: [PATCH 012/264] Added implementation of online multi-variate linear Regression --- .../OnlineMultiVariateRegressionImpl.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java new file mode 100644 index 00000000000..7088d342019 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java @@ -0,0 +1,60 @@ +package com.sap.sailing.polars.regression.impl; + +import com.sap.sailing.polars.regression.OnlineMultiVariateRegression; + +/** + * Allows online regression for multivariate linear functions as explained in: + * + * http://people.cs.pitt.edu/~milos/courses/cs2750-Spring03/lectures/class6.pdf (especially: Page 8+9) + * + * @author Frederik Petersen (D054528) + * + */ +public class OnlineMultiVariateRegressionImpl implements OnlineMultiVariateRegression { + + private double[] weights; + + private int addedPoints = 0; + + public OnlineMultiVariateRegressionImpl(int numberOfDimensions) { + weights = initializeWeightsArray(numberOfDimensions + 1); + } + + private double[] initializeWeightsArray(int arrayLength) { + double[] weights = new double[arrayLength]; + for (int i = 0; i < weights.length; i++) { + weights[i] = 0; + } + return weights; + } + + @Override + public void addData(double y, double[] x) { + checkNumberOfDimensions(x); + addedPoints++; + double[] newWeights = initializeWeightsArray(weights.length); + double alpha = 1.0/addedPoints; + newWeights[0] = weights[0] + alpha * (y - estimateY(x)); + for (int i = 1; i < weights.length; i++) { + newWeights[i] = weights[i] + alpha*(y - estimateY(x))*x[i-1]; + } + weights = newWeights; + } + + @Override + public double estimateY(double[] x) { + checkNumberOfDimensions(x); + double resultSum = weights[0]; + for (int i = 1; i < weights.length; i++) { + resultSum = resultSum + (weights[i] * x[i-1]); + } + return resultSum; + } + + private void checkNumberOfDimensions(double[] x) { + if (x.length + 1 != weights.length) { + throw new IllegalArgumentException("Dimension of x needs to be the same as specified in constructor."); + } + } + +} From 7504986f6b795818f5a4237fd751879403bc5cf0 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 21 Jan 2015 13:17:38 +0100 Subject: [PATCH 013/264] Added normalization of input for regression also added a test. it fails at this point due to some error.. --- ...nlineMultiVariateLinearRegressionTest.java | 36 +++++++++++++++++++ .../OnlineMultiVariateRegressionImpl.java | 33 +++++++++++++---- 2 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java new file mode 100644 index 00000000000..0ae80751c33 --- /dev/null +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java @@ -0,0 +1,36 @@ +package com.sap.sailing.polars.regression.test; + +import static org.junit.Assert.assertThat; + +import org.hamcrest.number.IsCloseTo; +import org.junit.Test; + +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sailing.polars.regression.OnlineMultiVariateRegression; +import com.sap.sailing.polars.regression.impl.OnlineMultiVariateRegressionImpl; + +public class OnlineMultiVariateLinearRegressionTest { + + private static double EPSILON = 1E-3; + + @Test + public void testRegressionWithABigAmountOfDataPointsAndOneDimension() throws NotEnoughDataHasBeenAddedException { + OnlineMultiVariateRegression regression = new OnlineMultiVariateRegressionImpl(1); + + for (int i = -1000; i < 1000; i++) { + // For every even number + 1, else - 1 + double y = i % 2 == 0 ? i + 1 : i - 1; + double[] x = { i }; + regression.addData(y, x); + } + + double[] x1 = { -2 }; + double y = regression.estimateY(x1); + assertThat(y, new IsCloseTo(-2, EPSILON)); + + double[] x2 = { 20 }; + y = regression.estimateY(x2); + assertThat(y, new IsCloseTo(20, EPSILON)); + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java index 7088d342019..8d9dbaca541 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java @@ -14,13 +14,22 @@ public class OnlineMultiVariateRegressionImpl implements OnlineMultiVariateRegre private double[] weights; - private int addedPoints = 0; + private int numberOfAddedPoints = 0; + + private final double[] means; + + private final double[] sumsOfSquaresOfDifferencesFromMean; + + private final double[] variances; public OnlineMultiVariateRegressionImpl(int numberOfDimensions) { - weights = initializeWeightsArray(numberOfDimensions + 1); + weights = initializeDoubleArray(numberOfDimensions + 1); + means = initializeDoubleArray(numberOfDimensions); + sumsOfSquaresOfDifferencesFromMean = initializeDoubleArray(numberOfDimensions); + variances = initializeDoubleArray(numberOfDimensions); } - private double[] initializeWeightsArray(int arrayLength) { + private double[] initializeDoubleArray(int arrayLength) { double[] weights = new double[arrayLength]; for (int i = 0; i < weights.length; i++) { weights[i] = 0; @@ -31,16 +40,26 @@ public class OnlineMultiVariateRegressionImpl implements OnlineMultiVariateRegre @Override public void addData(double y, double[] x) { checkNumberOfDimensions(x); - addedPoints++; - double[] newWeights = initializeWeightsArray(weights.length); - double alpha = 1.0/addedPoints; + numberOfAddedPoints++; + double[] newWeights = initializeDoubleArray(weights.length); + double alpha = 1.0/numberOfAddedPoints; newWeights[0] = weights[0] + alpha * (y - estimateY(x)); for (int i = 1; i < weights.length; i++) { - newWeights[i] = weights[i] + alpha*(y - estimateY(x))*x[i-1]; + incrementallyUpdateVariances(x, i); + double normalizedInput = (x[i-1] - means[i-1]) / Math.sqrt(variances[i-1]); + newWeights[i] = weights[i] + alpha*(y - estimateY(x))*normalizedInput; } weights = newWeights; } + private void incrementallyUpdateVariances(double[] x, int i) { + //http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Incremental_algorithm + double oldMean = means[i-1]; + means[i-1] = means[i-1] + ((x[i-1] - means[i-1]) / numberOfAddedPoints); + sumsOfSquaresOfDifferencesFromMean[i-1] = sumsOfSquaresOfDifferencesFromMean[i-1] + (x[i-1] - oldMean) * (x[i-1] - means[i-1]); + variances[i-1] = sumsOfSquaresOfDifferencesFromMean[i-1] / (numberOfAddedPoints - 1); + } + @Override public double estimateY(double[] x) { checkNumberOfDimensions(x); From be98b843243b4006dd7924769e6d537e7da11872 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 21 Jan 2015 18:19:18 +0100 Subject: [PATCH 014/264] Adjusted regression algorithm -used constant alpha -different test case -still a problem with huuuge weigths --- ...nlineMultiVariateLinearRegressionTest.java | 106 +++++++++++++++--- .../OnlineMultiVariateRegressionImpl.java | 4 +- 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java index 0ae80751c33..96903ebd8a3 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java @@ -1,8 +1,8 @@ package com.sap.sailing.polars.regression.test; +import static org.hamcrest.Matchers.closeTo; import static org.junit.Assert.assertThat; -import org.hamcrest.number.IsCloseTo; import org.junit.Test; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; @@ -11,26 +11,96 @@ import com.sap.sailing.polars.regression.impl.OnlineMultiVariateRegressionImpl; public class OnlineMultiVariateLinearRegressionTest { - private static double EPSILON = 1E-3; + private static double EPSILON = 0.5; @Test - public void testRegressionWithABigAmountOfDataPointsAndOneDimension() throws NotEnoughDataHasBeenAddedException { - OnlineMultiVariateRegression regression = new OnlineMultiVariateRegressionImpl(1); + public void testRegressionWithPolarData() throws NotEnoughDataHasBeenAddedException { + OnlineMultiVariateRegression regression = new OnlineMultiVariateRegressionImpl(3); + + double[] x1 = {1, 1, 1}; + regression.addData(-0.646600, x1); + double[] x2 = {2, 4, 8}; + regression.addData(0.764900, x2); + double[] x3 = {3, 9, 27}; + regression.addData(2.105400, x3); + double[] x4 = {4, 16, 64}; + regression.addData(3.376700, x4); + double[] x5 = {5, 25, 125}; + regression.addData(4.580600, x5); + double[] x6 = {6, 36, 216}; + regression.addData(5.718900, x6); + double[] x7 = {7, 49, 343}; + regression.addData(6.793400, x7); + double[] x8 = {8, 64, 512}; + regression.addData(7.805900, x8); + double[] x9 = {9, 81, 729}; + regression.addData(8.758200, x9); + double[] x10 = {10, 100, 1000}; + regression.addData(9.652100, x10); + double[] x11 = {11, 121, 1331}; + regression.addData(10.489400, x11); + double[] x12 = {12, 144, 1728}; + regression.addData(11.271900, x12); + double[] x13 = {13, 169, 2197}; + regression.addData(12.001400, x13); + double[] x14 = {14, 196, 2744}; + regression.addData(12.679700, x14); + double[] x15 = {15, 225, 3375}; + regression.addData(13.308600, x15); + double[] x16 = {16, 256, 4096}; + regression.addData(13.889900, x16); + double[] x17 = {17, 289, 4913}; + regression.addData(14.425400, x17); + double[] x18 = {18, 324, 5832}; + regression.addData(14.916900, x18); + double[] x19 = {19, 361, 6859}; + regression.addData(15.366200, x19); + double[] x20 = {20, 400, 8000}; + regression.addData(15.775100, x20); + double[] x21 = {21, 441, 9261}; + regression.addData(16.145400, x21); + double[] x22 = {22, 484, 10648}; + regression.addData(16.478900, x22); + double[] x23 = {23, 529, 12167}; + regression.addData(16.777400, x23); + double[] x24 = {24, 576, 13824}; + regression.addData(17.042700, x24); + double[] x25 = {25, 625, 15625}; + regression.addData(17.276600, x25); + double[] x26 = {26, 676, 17576}; + regression.addData(17.480900, x26); + double[] x27 = {27, 729, 19683}; + regression.addData(17.657400, x27); + double[] x28 = {28, 784, 21952}; + regression.addData(17.807900, x28); + double[] x29 = {29, 841, 24389}; + regression.addData(17.934200, x29); + double[] x30 = {30, 900, 27000}; + regression.addData(18.038100, x30); + double[] x31 = {31, 961, 29791}; + regression.addData(18.121400, x31); + double[] x32 = {32, 1024, 32768}; + regression.addData(18.185900, x32); + double[] x33 = {33, 1089, 35937}; + regression.addData(18.233400, x33); + double[] x34 = {34, 1156, 39304}; + regression.addData(18.265700, x34); + double[] x35 = {35, 1225, 42875}; + regression.addData(18.284600, x35); + double[] x36 = {36, 1296, 46656}; + regression.addData(18.291900, x36); + double[] x37 = {37, 1369, 50653}; + regression.addData(18.289400, x37); + double[] x38 = {38, 1444, 54872}; + regression.addData(18.278900, x38); + double[] x39 = {39, 1521, 59319}; + regression.addData(18.262200, x39); + double[] x40 = {40, 1600, 64000}; + regression.addData(18.241100, x40); + + double estimatedY = regression.estimateY(x27); + assertThat(estimatedY, closeTo(17.657400, EPSILON)); - for (int i = -1000; i < 1000; i++) { - // For every even number + 1, else - 1 - double y = i % 2 == 0 ? i + 1 : i - 1; - double[] x = { i }; - regression.addData(y, x); - } - - double[] x1 = { -2 }; - double y = regression.estimateY(x1); - assertThat(y, new IsCloseTo(-2, EPSILON)); - - double[] x2 = { 20 }; - y = regression.estimateY(x2); - assertThat(y, new IsCloseTo(20, EPSILON)); } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java index 8d9dbaca541..ce306a23312 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java @@ -42,12 +42,12 @@ public class OnlineMultiVariateRegressionImpl implements OnlineMultiVariateRegre checkNumberOfDimensions(x); numberOfAddedPoints++; double[] newWeights = initializeDoubleArray(weights.length); - double alpha = 1.0/numberOfAddedPoints; + double alpha = 0.01; newWeights[0] = weights[0] + alpha * (y - estimateY(x)); for (int i = 1; i < weights.length; i++) { incrementallyUpdateVariances(x, i); double normalizedInput = (x[i-1] - means[i-1]) / Math.sqrt(variances[i-1]); - newWeights[i] = weights[i] + alpha*(y - estimateY(x))*normalizedInput; + newWeights[i] = weights[i] + Math.pow(alpha, i)*(y - estimateY(x)) * x[i-1];//normalizedInput; } weights = newWeights; } From 393a5e1b37f771bb3594406e44313363a5751ffe Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Thu, 22 Jan 2015 11:04:23 +0100 Subject: [PATCH 015/264] Adjusted testing scenario and regression itself to christophers R example --- ...nlineMultiVariateLinearRegressionTest.java | 104 ++++-------------- .../OnlineMultiVariateRegressionImpl.java | 4 +- 2 files changed, 24 insertions(+), 84 deletions(-) diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java index 96903ebd8a3..e0ddfc1707d 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java @@ -3,6 +3,8 @@ package com.sap.sailing.polars.regression.test; import static org.hamcrest.Matchers.closeTo; import static org.junit.Assert.assertThat; +import java.util.Random; + import org.junit.Test; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; @@ -16,91 +18,29 @@ public class OnlineMultiVariateLinearRegressionTest { @Test public void testRegressionWithPolarData() throws NotEnoughDataHasBeenAddedException { OnlineMultiVariateRegression regression = new OnlineMultiVariateRegressionImpl(3); + Random random = new Random(9999); + + for (int i = 0; i < 1000; i++) { + double x = random.nextDouble() * 20 - 10; + double[] xArray = {x, x*x, x*x*x}; + regression.addData(function(x, random), xArray); + } + + double[] xArray = {0, 0, 0}; + double estimated = regression.estimateY(xArray); + + assertThat(estimated, closeTo(function(0, random), EPSILON)); - double[] x1 = {1, 1, 1}; - regression.addData(-0.646600, x1); - double[] x2 = {2, 4, 8}; - regression.addData(0.764900, x2); - double[] x3 = {3, 9, 27}; - regression.addData(2.105400, x3); - double[] x4 = {4, 16, 64}; - regression.addData(3.376700, x4); - double[] x5 = {5, 25, 125}; - regression.addData(4.580600, x5); - double[] x6 = {6, 36, 216}; - regression.addData(5.718900, x6); - double[] x7 = {7, 49, 343}; - regression.addData(6.793400, x7); - double[] x8 = {8, 64, 512}; - regression.addData(7.805900, x8); - double[] x9 = {9, 81, 729}; - regression.addData(8.758200, x9); - double[] x10 = {10, 100, 1000}; - regression.addData(9.652100, x10); - double[] x11 = {11, 121, 1331}; - regression.addData(10.489400, x11); - double[] x12 = {12, 144, 1728}; - regression.addData(11.271900, x12); - double[] x13 = {13, 169, 2197}; - regression.addData(12.001400, x13); - double[] x14 = {14, 196, 2744}; - regression.addData(12.679700, x14); - double[] x15 = {15, 225, 3375}; - regression.addData(13.308600, x15); - double[] x16 = {16, 256, 4096}; - regression.addData(13.889900, x16); - double[] x17 = {17, 289, 4913}; - regression.addData(14.425400, x17); - double[] x18 = {18, 324, 5832}; - regression.addData(14.916900, x18); - double[] x19 = {19, 361, 6859}; - regression.addData(15.366200, x19); - double[] x20 = {20, 400, 8000}; - regression.addData(15.775100, x20); - double[] x21 = {21, 441, 9261}; - regression.addData(16.145400, x21); - double[] x22 = {22, 484, 10648}; - regression.addData(16.478900, x22); - double[] x23 = {23, 529, 12167}; - regression.addData(16.777400, x23); - double[] x24 = {24, 576, 13824}; - regression.addData(17.042700, x24); - double[] x25 = {25, 625, 15625}; - regression.addData(17.276600, x25); - double[] x26 = {26, 676, 17576}; - regression.addData(17.480900, x26); - double[] x27 = {27, 729, 19683}; - regression.addData(17.657400, x27); - double[] x28 = {28, 784, 21952}; - regression.addData(17.807900, x28); - double[] x29 = {29, 841, 24389}; - regression.addData(17.934200, x29); - double[] x30 = {30, 900, 27000}; - regression.addData(18.038100, x30); - double[] x31 = {31, 961, 29791}; - regression.addData(18.121400, x31); - double[] x32 = {32, 1024, 32768}; - regression.addData(18.185900, x32); - double[] x33 = {33, 1089, 35937}; - regression.addData(18.233400, x33); - double[] x34 = {34, 1156, 39304}; - regression.addData(18.265700, x34); - double[] x35 = {35, 1225, 42875}; - regression.addData(18.284600, x35); - double[] x36 = {36, 1296, 46656}; - regression.addData(18.291900, x36); - double[] x37 = {37, 1369, 50653}; - regression.addData(18.289400, x37); - double[] x38 = {38, 1444, 54872}; - regression.addData(18.278900, x38); - double[] x39 = {39, 1521, 59319}; - regression.addData(18.262200, x39); - double[] x40 = {40, 1600, 64000}; - regression.addData(18.241100, x40); - double estimatedY = regression.estimateY(x27); - assertThat(estimatedY, closeTo(17.657400, EPSILON)); } + + private double function(double x) { + return 1 + x*0.2 - x*x + 0.1*x*x*x; + } + + private double function(double x, Random random) { + return function(x) + random.nextGaussian() * 5; + } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java index ce306a23312..8c22e61c837 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java @@ -42,12 +42,12 @@ public class OnlineMultiVariateRegressionImpl implements OnlineMultiVariateRegre checkNumberOfDimensions(x); numberOfAddedPoints++; double[] newWeights = initializeDoubleArray(weights.length); - double alpha = 0.01; + double alpha = 0.03; newWeights[0] = weights[0] + alpha * (y - estimateY(x)); for (int i = 1; i < weights.length; i++) { incrementallyUpdateVariances(x, i); double normalizedInput = (x[i-1] - means[i-1]) / Math.sqrt(variances[i-1]); - newWeights[i] = weights[i] + Math.pow(alpha, i)*(y - estimateY(x)) * x[i-1];//normalizedInput; + newWeights[i] = weights[i] + alpha * (y - estimateY(x)) * Math.pow(alpha, i) * x[i-1];//normalizedInput; } weights = newWeights; } From 78012982690ebde278ba8d07c8f1189ccdf156ff Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Thu, 22 Jan 2015 19:11:40 +0100 Subject: [PATCH 016/264] Some changes to the online regression input normalization is not functioning yet --- ...nlineMultiVariateLinearRegressionTest.java | 17 +++++++-------- .../OnlineMultiVariateRegressionImpl.java | 21 ++++++++++++++----- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java index e0ddfc1707d..7ca7885dde3 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java @@ -13,30 +13,27 @@ import com.sap.sailing.polars.regression.impl.OnlineMultiVariateRegressionImpl; public class OnlineMultiVariateLinearRegressionTest { - private static double EPSILON = 0.5; + private static double EPSILON = 1; @Test public void testRegressionWithPolarData() throws NotEnoughDataHasBeenAddedException { OnlineMultiVariateRegression regression = new OnlineMultiVariateRegressionImpl(3); - Random random = new Random(9999); + Random random = new Random(1); - for (int i = 0; i < 1000; i++) { - double x = random.nextDouble() * 20 - 10; + for (int i = 0; i < 10000; i++) { + double x = random.nextDouble() * 80 - 40; double[] xArray = {x, x*x, x*x*x}; regression.addData(function(x, random), xArray); } - double[] xArray = {0, 0, 0}; + double[] xArray = {5, 25, 125}; double estimated = regression.estimateY(xArray); - assertThat(estimated, closeTo(function(0, random), EPSILON)); - - - + assertThat(estimated, closeTo(function(5, random), EPSILON)); } private double function(double x) { - return 1 + x*0.2 - x*x + 0.1*x*x*x; + return 0.0003*x*x*x-0.0373*x*x+1.5213*x -2.1309; } private double function(double x, Random random) { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java index 8c22e61c837..fc2698548cf 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java @@ -42,22 +42,32 @@ public class OnlineMultiVariateRegressionImpl implements OnlineMultiVariateRegre checkNumberOfDimensions(x); numberOfAddedPoints++; double[] newWeights = initializeDoubleArray(weights.length); - double alpha = 0.03; + double alpha = 0.005; newWeights[0] = weights[0] + alpha * (y - estimateY(x)); for (int i = 1; i < weights.length; i++) { incrementallyUpdateVariances(x, i); - double normalizedInput = (x[i-1] - means[i-1]) / Math.sqrt(variances[i-1]); - newWeights[i] = weights[i] + alpha * (y - estimateY(x)) * Math.pow(alpha, i) * x[i-1];//normalizedInput; + double normalizedInput = normalizeInput(x, i - 1); + newWeights[i] = weights[i] + (y - estimateY(x)) * Math.pow(alpha, i + 1) * x[i - 1];// normalizedInput; } weights = newWeights; } + private double normalizeInput(double[] x, int dimensionIndex) { + double normalizedInput; + if (variances[dimensionIndex] > 0) { + normalizedInput = (x[dimensionIndex] - means[dimensionIndex]) / Math.sqrt(variances[dimensionIndex]); + } else { + normalizedInput = x[dimensionIndex]; + } + return normalizedInput; + } + private void incrementallyUpdateVariances(double[] x, int i) { //http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Incremental_algorithm double oldMean = means[i-1]; means[i-1] = means[i-1] + ((x[i-1] - means[i-1]) / numberOfAddedPoints); sumsOfSquaresOfDifferencesFromMean[i-1] = sumsOfSquaresOfDifferencesFromMean[i-1] + (x[i-1] - oldMean) * (x[i-1] - means[i-1]); - variances[i-1] = sumsOfSquaresOfDifferencesFromMean[i-1] / (numberOfAddedPoints - 1); + variances[i-1] = sumsOfSquaresOfDifferencesFromMean[i-1] / numberOfAddedPoints ; } @Override @@ -65,7 +75,8 @@ public class OnlineMultiVariateRegressionImpl implements OnlineMultiVariateRegre checkNumberOfDimensions(x); double resultSum = weights[0]; for (int i = 1; i < weights.length; i++) { - resultSum = resultSum + (weights[i] * x[i-1]); + double normalizedInput = normalizeInput(x, i - 1); + resultSum = resultSum + (weights[i] * x[i - 1]); } return resultSum; } From 2cf6703ff6ee86259aaf11dd2dedd7b9aca5bc9d Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 3 Feb 2015 16:13:48 +0100 Subject: [PATCH 017/264] intial commit of a bundle for a UI component library for SSE --- .../SailingServer (No Proxy).launch | 2 +- java/com.sap.sse.gwt.theme/.classpath | 10 + java/com.sap.sse.gwt.theme/.gitignore | 6 + java/com.sap.sse.gwt.theme/.project | 39 ++ .../com.google.gwt.eclipse.core.prefs | 2 + .../META-INF/MANIFEST.MF | 19 + java/com.sap.sse.gwt.theme/SSETheme.css | 3 + java/com.sap.sse.gwt.theme/SSETheme.html | 54 +++ .../SSETheme_GWT sdm.launch | 27 ++ .../com.sap.sse.gwt.theme/SSETheme_GWT.launch | 26 + java/com.sap.sse.gwt.theme/WEB-INF/.gitignore | 2 + java/com.sap.sse.gwt.theme/WEB-INF/web.xml | 22 + java/com.sap.sse.gwt.theme/build.properties | 10 + java/com.sap.sse.gwt.theme/pom.xml | 107 +++++ .../com/sap/sse/gwt/theme/SSETheme.gwt.xml | 25 + .../sse/gwt/theme/client/ThemeEntryPoint.java | 20 + .../component/recentevent/RecentEvent.css | 47 ++ .../component/recentevent/RecentEvent.java | 69 +++ .../component/recentevent/RecentEvent.ui.xml | 22 + .../recentevent/RecentEventData.java | 16 + .../recentevent/RecentEventResources.java | 25 + .../recentevent/default_event_photo.jpg | Bin 0 -> 28976 bytes .../client/component/sapheader/SAPHeader.css | 44 ++ .../client/component/sapheader/SAPHeader.java | 51 ++ .../component/sapheader/SAPHeader.ui.xml | 21 + .../component/sapheader/SAPHeaderData.java | 5 + .../sapheader/SAPHeaderResources.java | 26 + .../component/sapheader/logo-small@2x.png | Bin 0 -> 6388 bytes .../client/resources/ThemeResources.java | 140 ++++++ .../client/resources/i18n/TextMessages.java | 10 + .../resources/i18n/TextMessages.properties | 1 + .../resources/i18n/TextMessages_de.properties | 1 + .../sse/gwt/theme/client/resources/main.gss | 417 ++++++++++++++++ .../sse/gwt/theme/client/resources/media.gss | 450 ++++++++++++++++++ .../client/showcase/ComponentShowCase.java | 53 +++ .../client/showcase/ComponentShowCase.ui.xml | 30 ++ .../gwt/theme/client/showcase/MainPage.java | 30 ++ .../gwt/theme/client/showcase/MainPage.ui.xml | 33 ++ .../gwt/theme/client/showcase/Section.java | 50 ++ .../gwt/theme/client/showcase/Section.ui.xml | 6 + .../showcase/ShowcaseStackLayoutPanel.java | 27 ++ .../recentevent/RecentEventShowcase.java | 131 +++++ .../recentevent/RecentEventShowcase.ui.xml | 35 ++ .../showcase/sapheader/SAPHeaderShowcase.java | 25 + .../sapheader/SAPHeaderShowcase.ui.xml | 29 ++ .../client/styleguide/typeface/typeface.html | 24 + .../gwt/theme/client/util/FullscreenUtil.java | 27 ++ 47 files changed, 2218 insertions(+), 1 deletion(-) create mode 100644 java/com.sap.sse.gwt.theme/.classpath create mode 100644 java/com.sap.sse.gwt.theme/.gitignore create mode 100644 java/com.sap.sse.gwt.theme/.project create mode 100644 java/com.sap.sse.gwt.theme/.settings/com.google.gwt.eclipse.core.prefs create mode 100644 java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF create mode 100644 java/com.sap.sse.gwt.theme/SSETheme.css create mode 100644 java/com.sap.sse.gwt.theme/SSETheme.html create mode 100644 java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch create mode 100644 java/com.sap.sse.gwt.theme/SSETheme_GWT.launch create mode 100644 java/com.sap.sse.gwt.theme/WEB-INF/.gitignore create mode 100644 java/com.sap.sse.gwt.theme/WEB-INF/web.xml create mode 100644 java/com.sap.sse.gwt.theme/build.properties create mode 100644 java/com.sap.sse.gwt.theme/pom.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/ThemeEntryPoint.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.css create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventData.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventResources.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/default_event_photo.jpg create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.css create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderData.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/logo-small@2x.png create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/ThemeResources.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/main.gss create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/media.gss create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowcaseStackLayoutPanel.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/styleguide/typeface/typeface.html create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/util/FullscreenUtil.java diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 8d2cd64f937..8f35a451c9c 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -33,5 +33,5 @@ - + diff --git a/java/com.sap.sse.gwt.theme/.classpath b/java/com.sap.sse.gwt.theme/.classpath new file mode 100644 index 00000000000..b983a4da697 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/.gitignore b/java/com.sap.sse.gwt.theme/.gitignore new file mode 100644 index 00000000000..62f0b377a8a --- /dev/null +++ b/java/com.sap.sse.gwt.theme/.gitignore @@ -0,0 +1,6 @@ +/bin +/.gwt +/.generated +/gwt-unitCache +/extras +/com.sap.sse.gwt.theme.* diff --git a/java/com.sap.sse.gwt.theme/.project b/java/com.sap.sse.gwt.theme/.project new file mode 100644 index 00000000000..8e679e93a05 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/.project @@ -0,0 +1,39 @@ + + + com.sap.sse.gwt.theme + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + com.google.gdt.eclipse.core.webAppProjectValidator + + + + + com.google.gwt.eclipse.core.gwtProjectValidator + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + com.google.gwt.eclipse.core.gwtNature + + diff --git a/java/com.sap.sse.gwt.theme/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sse.gwt.theme/.settings/com.google.gwt.eclipse.core.prefs new file mode 100644 index 00000000000..e992774d99b --- /dev/null +++ b/java/com.sap.sse.gwt.theme/.settings/com.google.gwt.eclipse.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgSU5GT11dPjwvZXh0cmEtYXJncz48dm0tYXJncz48IVtDREFUQVstWG14NTEybV1dPjwvdm0tYXJncz48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc3NlLmd3dC50aGVtZS5TU0VUaGVtZTwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+ diff --git a/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..ff236c0cf0d --- /dev/null +++ b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF @@ -0,0 +1,19 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: SSE Base Theme +Bundle-SymbolicName: com.sap.sse.gwt.theme +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-ActivationPolicy: lazy +Bundle-ClassPath: WEB-INF/classes/ +Import-Package: org.osgi.framework;version="1.7.0", + javax.servlet;version="3.1.0", + javax.servlet.descriptor;version="3.1.0", + javax.servlet.http;version="3.1.0" +Require-Bundle: com.google.gwt.osgi;bundle-version="2.7.0", + com.sap.sailing.domain.common, + com.sap.sse.common, + com.sap.sse.gwt +Web-ContextPath: /theme +Export-Package: com.sap.sse.gwt.theme.client diff --git a/java/com.sap.sse.gwt.theme/SSETheme.css b/java/com.sap.sse.gwt.theme/SSETheme.css new file mode 100644 index 00000000000..9a49fb97ef1 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/SSETheme.css @@ -0,0 +1,3 @@ +.mainPanel { + +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/SSETheme.html b/java/com.sap.sse.gwt.theme/SSETheme.html new file mode 100644 index 00000000000..cb45c7aad46 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/SSETheme.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + SAP Sports Engine Theme + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch b/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch new file mode 100644 index 00000000000..e61889df28b --- /dev/null +++ b/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch b/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch new file mode 100644 index 00000000000..497ba323966 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore b/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore new file mode 100644 index 00000000000..b30e141e749 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore @@ -0,0 +1,2 @@ +deploy +classes \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/WEB-INF/web.xml b/java/com.sap.sse.gwt.theme/WEB-INF/web.xml new file mode 100644 index 00000000000..7afb56e6574 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/WEB-INF/web.xml @@ -0,0 +1,22 @@ + + + + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + dirAllowed + false + + + useFileMappedBuffer + false + + + + diff --git a/java/com.sap.sse.gwt.theme/build.properties b/java/com.sap.sse.gwt.theme/build.properties new file mode 100644 index 00000000000..95d385246a8 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/build.properties @@ -0,0 +1,10 @@ +source.WEB-INF/classes/ = src/main/java,\ + src/main/resources +output.WEB-INF/classes/ = bin +bin.includes = META-INF/,\ + WEB-INF/classes/,\ + WEB-INF/web.xml,\ + com.sap.sse.gwt.theme.SSETheme/,\ + SSETheme.css,\ + SSETheme.html +output.. = WEB-INF/classes/ diff --git a/java/com.sap.sse.gwt.theme/pom.xml b/java/com.sap.sse.gwt.theme/pom.xml new file mode 100644 index 00000000000..b426d91ca1a --- /dev/null +++ b/java/com.sap.sse.gwt.theme/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sse.gwt.theme + eclipse-plugin + + + javax.validation + validation-api + 1.0.0.GA + + + javax.validation + validation-api + 1.0.0.GA + sources + + + com.sap.sailing + com.sap.sse.common + 1.0.0-SNAPSHOT + sources + + + com.sap.sailing + com.sap.sse.gwt + 1.0.0-SNAPSHOT + sources + + + + + + org.eclipse.tycho + tycho-source-plugin + ${tycho-version} + + + plugin-source + generate-sources + + plugin-source + + + + + + org.codehaus.mojo + gwt-maven-plugin + ${gwt.version} + + + + + -Dgwt.persistentunitcache=false + + + com.sap.sse.gwt.theme.Theme + + ${basedir}/.generated + 7 + ${basedir} + ${basedir}/src/main/resources + + + + compile + + + + + + + + + + debug.no-gwt-compile + + + + org.codehaus.mojo + gwt-maven-plugin + ${gwt.version} + + + + + + + + help + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml new file mode 100644 index 00000000000..8af42f07f54 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/ThemeEntryPoint.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/ThemeEntryPoint.java new file mode 100644 index 00000000000..99665fc24e3 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/ThemeEntryPoint.java @@ -0,0 +1,20 @@ +package com.sap.sse.gwt.theme.client; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.sap.sse.gwt.theme.client.resources.ThemeResources; +import com.sap.sse.gwt.theme.client.showcase.MainPage; + +/** + * Entry point classes define onModuleLoad(). + */ +public class ThemeEntryPoint implements EntryPoint { + + @Override + public void onModuleLoad() { + ThemeResources.INSTANCE.mediaCss().ensureInjected(); + ThemeResources.INSTANCE.mainCss().ensureInjected(); + + RootLayoutPanel.get().add(new MainPage()); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.css b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.css new file mode 100644 index 00000000000..519353979c9 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.css @@ -0,0 +1,47 @@ +.eventteasercontainer { + font-size: 1rem; + text-align: center; + width: 100%; + height: 24em; +} +.eventteaser { + font-size: 1rem; + display: inline-block; + margin-bottom: 3em; + text-align: left; + max-width: 100%; + width: 100%; +} +.eventteaser:hover { + text-decoration: none; +} +.eventteaser:hover .eventteaser_name { + text-decoration: underline; +} +.eventteaser:hover .eventteaser_image { + opacity: 0.9; +} +.eventteaser_image { + position: relative; + display: inline-block; + max-width: 100%; + width: 100%; + height: 240px; + background-position: center; + background-size: cover; + background-repeat: no-repeat; +} +.eventteaser_series { + position: absolute; + bottom: 0; + right: 0; +} +.eventteaser_name { + font-size: 1.466666666666667em; + font-weight: bold; + color: #008fcc; + margin-top: 0.333333333333333em; +} +.eventteaser_location { + color: #222; +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.java new file mode 100644 index 00000000000..b5fde43544c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.java @@ -0,0 +1,69 @@ +package com.sap.sse.gwt.theme.client.component.recentevent; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class RecentEvent extends Composite { + + @UiField + SpanElement eventNameUi; + + @UiField + SpanElement venueNameUi; + + @UiField + SpanElement eventDateUi; + + @UiField + Anchor eventOverviewUi; + + @UiField + DivElement eventImageContainerUi; + + @UiField + DivElement isLiveUi; + + interface RecentEventUiBinder extends UiBinder { + } + + private static RecentEventUiBinder uiBinder = GWT.create(RecentEventUiBinder.class); + private Command eventClickAction; + + public RecentEvent(RecentEventData data) { + + this.eventClickAction = data.getCommand(); + RecentEventResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + + eventNameUi.setInnerText(data.getEventName()); + venueNameUi.setInnerText(data.getVenue()); + eventDateUi.setInnerText(data.getEventStart().toString().substring(10) + " " + + data.getEventEnd().toString().substring(10)); + + final StringBuilder thumbnailUrlBuilder = new StringBuilder("url('").append(data.getEventImageUrl()).append( + "')"); + GWT.log(thumbnailUrlBuilder.toString()); + eventImageContainerUi.getStyle().setBackgroundImage(thumbnailUrlBuilder.toString()); + if (data.isLive()) + isLiveUi.getStyle().setDisplay(Display.BLOCK); + else + isLiveUi.getStyle().setDisplay(Display.NONE); + } + + @UiHandler("eventOverviewUi") + public void goToEventOverview(ClickEvent e) { + eventClickAction.execute(); + e.preventDefault(); + } + +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.ui.xml new file mode 100644 index 00000000000..92b0202e911 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.ui.xml @@ -0,0 +1,22 @@ + + + + + + + + +
+ +
+
+
+
+
+ ,  +
+
+
+
+
diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventData.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventData.java new file mode 100644 index 00000000000..b3788eaab89 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventData.java @@ -0,0 +1,16 @@ +package com.sap.sse.gwt.theme.client.component.recentevent; + +import java.util.Date; + +import com.google.gwt.user.client.Command; + +public interface RecentEventData { + String getEventImageUrl(); + String getVenue(); + String getLocation(); + Date getEventStart(); + Date getEventEnd(); + Command getCommand(); + String getEventName(); + boolean isLive(); +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventResources.java new file mode 100644 index 00000000000..6d2229572a1 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventResources.java @@ -0,0 +1,25 @@ +package com.sap.sse.gwt.theme.client.component.recentevent; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; + +public interface RecentEventResources extends ClientBundle { + public static final RecentEventResources INSTANCE = GWT.create(RecentEventResources.class); + + @Source("RecentEvent.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String eventteaser(); + String eventteaser_name(); + String eventteaser_image(); + String eventteaser_series(); + String eventteaser_location(); + String eventteasercontainer(); + } + + @Source("default_event_photo.jpg") + ImageResource defaultEventPhotoImage(); +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/default_event_photo.jpg b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/default_event_photo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03f41052084a0cb160d540dc85c7ab3f9459db39 GIT binary patch literal 28976 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 literal 0 HcmV?d00001 diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.css b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.css new file mode 100644 index 00000000000..9de247011bb --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.css @@ -0,0 +1,44 @@ +.siteheader { + font-size: 15px; + font-size: 1rem; + background: #333; + border-bottom: 5px solid #f0ab00; + height: 70px; + line-height: 4em; + width: 100%; + overflow: hidden; + text-align:center; +} + +.siteheaderLeft { + float:left; + width: 25%; + text-align: left; +} + +.siteheaderCentered { + width: 50%; + display: inline-block; + margin:0 auto; +} + +.siteheaderRight { + float:right; + width: 25%; + text-align: right; +} + +.logo { + margin: 0.8em 10px; + float: left; +} +.logotitle { + display: inline; + color: #f6f9fc; + font-weight: bold; +} +.pagetitle { + color: #ffffff; + font-size: 1.5rem; + font-weight: bold; +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.java new file mode 100644 index 00000000000..83e30aaa36f --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.java @@ -0,0 +1,51 @@ +package com.sap.sse.gwt.theme.client.component.sapheader; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.RepeatingCommand; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.ParagraphElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.util.FullscreenUtil; + +public class SAPHeader extends Composite { + private static SAPHeaderUiBinder uiBinder = GWT.create(SAPHeaderUiBinder.class); + + interface SAPHeaderUiBinder extends UiBinder { + } + + @UiField ParagraphElement applicationNameUi; + @UiField DivElement pageTitleUi; + @UiField Button startFullScreenButton; + + public SAPHeader(String applicationName, String pageTitle, boolean startInAutoScreenMode) { + SAPHeaderResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + + applicationNameUi.setInnerText(applicationName != null ? applicationName : " "); + pageTitleUi.setInnerText(pageTitle); + startFullScreenButton.setVisible(startInAutoScreenMode); + + // the 'fullscreen' button should disappear after some seconds (10) + if(startInAutoScreenMode) { + Scheduler.get().scheduleFixedPeriod(new RepeatingCommand() { + public boolean execute () { + startFullScreenButton.setVisible(false); + return false; + } + }, 10000); + } + } + + @UiHandler("startFullScreenButton") + void startFullScreenClicked(ClickEvent e) { + FullscreenUtil.requestFullscreen(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.ui.xml new file mode 100644 index 00000000000..1138ddaf708 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.ui.xml @@ -0,0 +1,21 @@ + + + + + +
+
+ +

+
+
+
+
+
+ +   +
+
+
+
diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderData.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderData.java new file mode 100644 index 00000000000..16804993bd9 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderData.java @@ -0,0 +1,5 @@ +package com.sap.sse.gwt.theme.client.component.sapheader; + +public interface SAPHeaderData { + +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java new file mode 100644 index 00000000000..cea0a1754d1 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java @@ -0,0 +1,26 @@ +package com.sap.sse.gwt.theme.client.component.sapheader; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; + +public interface SAPHeaderResources extends ClientBundle { + public static final SAPHeaderResources INSTANCE = GWT.create(SAPHeaderResources.class); + + @Source("SAPHeader.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String siteheader(); + String siteheaderLeft(); + String siteheaderCentered(); + String siteheaderRight(); + String logo(); + String logotitle(); + String pagetitle(); + } + + @Source("logo-small@2x.png") + ImageResource logo(); +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/logo-small@2x.png b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/logo-small@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e5d2daf91f3962deb5be2991141b8145186e982a GIT binary patch literal 6388 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 NVW?-STdnOB^*{MHr`-Sm literal 0 HcmV?d00001 diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/ThemeResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/ThemeResources.java new file mode 100644 index 00000000000..d6026a3ab87 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/ThemeResources.java @@ -0,0 +1,140 @@ +package com.sap.sse.gwt.theme.client.resources; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface ThemeResources extends ClientBundle { + public static final ThemeResources INSTANCE = GWT.create(ThemeResources.class); + + @Source("main.gss") + MainCss mainCss(); + + @Source("media.gss") + MediaCss mediaCss(); + + public interface MainCss extends CssResource{ + String wrapper(); + String navbar(); + String navbar_button(); + String navbar_buttonhidden(); + String navbar_buttonactive(); + String button(); + String buttonsmall(); + String buttonstrong(); + String buttoninactive(); + String buttonprimary(); + String buttonred(); + String buttonrefresh(); + String buttonarrowdown(); + String buttonsearch(); + String buttonarrowrightwhite(); + String label(); + String labellarge(); + String morelink(); + String dummy(); + String mainsection(); + String mainsection_header(); + String mainsection_header_title(); + String mainsection_navigation(); + String lightbox(); + String lightbox_overlay(); + String lightbox_content(); + String lightbox_close(); + String stickyheader(); + String slideto(); + String slidetonav(); + String slidetoactive(); + } + + public interface MediaCss extends CssResource{ + String grid(); + String stackenblochen(); + String column(); + String columns(); + String small1(); + String small2(); + String small3(); + String small4(); + String small5(); + String small6(); + String small7(); + String small8(); + String small9(); + String small10(); + String small11(); + String small12(); + String smalloffset0(); + String smalloffset1(); + String smalloffset2(); + String smalloffset3(); + String smalloffset4(); + String smalloffset5(); + String smalloffset6(); + String smalloffset7(); + String smalloffset8(); + String smalloffset9(); + String smalloffset10(); + String smalloffset11(); + String showonsmall(); + String hideonsmall(); + String showonmedium(); + String hideonmedium(); + String showonlarge(); + String hideonlarge(); + String smallcentered(); + String smalluncentered(); + String medium1(); + String medium2(); + String medium3(); + String medium4(); + String medium5(); + String medium6(); + String medium7(); + String medium8(); + String medium9(); + String medium10(); + String medium11(); + String medium12(); + String mediumoffset0(); + String mediumoffset1(); + String mediumoffset2(); + String mediumoffset3(); + String mediumoffset4(); + String mediumoffset5(); + String mediumoffset6(); + String mediumoffset7(); + String mediumoffset8(); + String mediumoffset9(); + String mediumoffset10(); + String mediumoffset11(); + String mediumcentered(); + String mediumuncentered(); + String large1(); + String large2(); + String large3(); + String large4(); + String large5(); + String large6(); + String large7(); + String large8(); + String large9(); + String large10(); + String large11(); + String large12(); + String largeoffset0(); + String largeoffset1(); + String largeoffset2(); + String largeoffset3(); + String largeoffset4(); + String largeoffset5(); + String largeoffset6(); + String largeoffset7(); + String largeoffset8(); + String largeoffset9(); + String largeoffset10(); + String largeoffset11(); + String largecentered(); + String largeuncentered(); + } +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java new file mode 100644 index 00000000000..3b9b9964efc --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java @@ -0,0 +1,10 @@ +package com.sap.sse.gwt.theme.client.resources.i18n; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.i18n.client.Messages; + +public interface TextMessages extends Messages { + public static final TextMessages INSTANCE = GWT.create(TextMessages.class); + + String chooseALanguage(); +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties new file mode 100644 index 00000000000..3bb701bcae5 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties @@ -0,0 +1 @@ +chooseALanguage=Choose a language diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties new file mode 100644 index 00000000000..c638bf7471d --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties @@ -0,0 +1 @@ +chooseALanguage=Wähle eine Sprache diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/main.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/main.gss new file mode 100644 index 00000000000..ea60fb681f6 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/main.gss @@ -0,0 +1,417 @@ +/** + * The main styling styles + */ +*, +*:before, +*:after { + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html, +body { + height: 100%; + font-family: 'Open Sans', sans-serif; + font-size: 93.75%; + line-height: 1.333; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -ms-content-zooming: none; +} +figure { + margin: 0; +} +a { + text-decoration: none; + color: #008fcc; +} +a:hover, +a:focus { + text-decoration: underline; +} +a img { + vertical-align: bottom; +} +hr { + font-size: 1rem; + margin: 0; + height: 1px; + border: none; + border-top: 1px solid #eee; +} +h1 { + font-size: 2.133333333333333em; +} +h2 { + font-size: 1.466666666666667em; + font-weight: bold; + color: #008fcc; + margin-top: 0.7em; + margin-bottom: 0.7em; +} +h3 { + font-size: 1.2em; + font-weight: bold; + margin: 0 0 0.5em; +} +h4 { + font-size: 1em; + font-weight: bold; + color: #008fcc; +} +h5 { + font-size: 0.866666666666667em; + font-weight: 600; + color: #008fcc; +} +figure img { + display: block; +} +figcaption { + margin-top: 1.333333333333333em; +} +.wrapper { + position: relative; + min-height: 100%; + margin-bottom: -5em; + padding-bottom: 8em; +} +.wrapper > * { + padding-left: 1.333333333333333em; + padding-right: 1.333333333333333em; +} +.navbar { + font-size: 1rem; +} +.navbar_button { + font-size: 1.2em; + display: inline-block; + font-weight: 600; + color: #666; + border: 1px solid #dedede; + padding: 0.833333333333333em 0.944444444444444em; + background-color: #eee; + -webkit-transition: background-color 0.2s, border-color 0.2s; + -moz-transition: background-color 0.2s, border-color 0.2s; + transition: background-color 0.2s, border-color 0.2s; + text-decoration: none; + margin-right: 0.833333333333333em; +} +.navbar_buttonhidden { + display: block; +} +.navbar_button:hover, +.navbar_button:focus { + background-color: #dedede; + outline: none; + text-decoration: none; +} +.navbar_buttonactive { + color: #333; + background-color: #fff; + border-bottom: 1px solid #fff; +} +.navbar_buttonactive:hover, +.navbar_buttonactive:focus { + background-color: #dedede; + border-bottom: 1px solid #dedede; +} +.button { + font-size: 1rem; + display: inline-block; + font-weight: 600; + color: #fff; + border: none; + padding: 0.666666666666667em 2em; + border-radius: 0.2em; + background-color: #f0ab00; + -webkit-transition: background-color 0.2s; + -moz-transition: background-color 0.2s; + transition: background-color 0.2s; + text-decoration: none; +} +.button:hover, +.button:focus { + background-color: #d89a00; + outline: none; + text-decoration: none; +} +.buttonsmall { + visibility: inherit; +} +.buttonstrong { + font-size: 0.933333333333333rem; + font-weight: 700; + text-transform: uppercase; + padding: 0.714285714285714em 1.071428571428571em; +} +.buttoninactive { + background-color: #eee; +} +.buttoninactive:hover, +.buttoninactive:focus { + background-color: #eee; +} +.buttonprimary { + background-color: #008fcc; +} +.buttonprimary:hover, +.buttonprimary:focus { + background-color: #0081b8; +} +.buttonred { + background-color: #e94a1b; +} +.buttonred:hover, +.buttonred:focus { + background-color: #d54115; +} +.buttonrefresh { + background-image: url("data:image/png;base64,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"); +} +.buttonrefresh, +.buttonarrowdown { + background-repeat: no-repeat; + background-position: left top; + background-size: cover; + width: 2.333333333333333em; + height: 0; + padding: 2.333333333333333em 0 0 0; + border: 0; + overflow: hidden; +} +.buttonsearch { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2NTg0MEE1MTVFN0UxMUU0QkNGMTk0Q0UzOUEwNUNFMyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2NTg0MEE1MjVFN0UxMUU0QkNGMTk0Q0UzOUEwNUNFMyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjY1ODQwQTRGNUU3RTExRTRCQ0YxOTRDRTM5QTA1Q0UzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY1ODQwQTUwNUU3RTExRTRCQ0YxOTRDRTM5QTA1Q0UzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+tERf2gAAArFJREFUeNrMmE1kXFEUx28SwjAMjxCGIQwhzCoMQysVugqlhJBVyCqLbELpqtmEUFJddZFNFhVSpd20uqgw1VIhixgSIqREYiKRSExkhNTruZzhOO5999737m3m8DdzZt6773fP/Trn9cRxLBysD/QUNAaKQEP4eQI6xc/voN/Cl0lAC1VBG6Cr2M6aoHegkmX7WpkuKIPW4/R2B1oBRSEAJ0G3BoAz0DaoZbjuGEfBG+CS5kG7oFegiua+MYxYU3Gv7OyUD0AV3CFo2qHhHGhBM2efZQGcUjT4ER+YZg7JRbLF2msljEAiYFkx515nXYXYuS+s3QNQvyvghiJywpPyoAZr/4ULYFXRw5xHQKkh3HY6dmGz/fTifv2S7d+LoLbwa39Ab4gvT6BZm5MkYj3b9Rw5qojN84ZNBB+B+gnzexHOLvGs7lgFVEy6oQNI7asIax+YP2oCLBP/BrQXGHCH+RUT4ADxZcr0NzDgEfMLJsA88dsivN0wP2cCvCZ+/j8ARsw/NwGeEr+IWXNIK7kC7hNfbje1wIA1xQaeCPiL/TYRGHCSzfkfppMkx/K2Q9tMI2X6dU+e9dPmJGmzzVNWanOBorfE5viqbVWnyjSKnqNXY9E7thkp6qywlGvLY8pVVNQpc64Ja4S9ovbJA2QJMyRqm2lrkqoi7W9kKMBrmgpvPEtVN60pGZdBBYeorRnePAxnqYufa4rxFg677MQoSdlL6MtS8xtbDJkgk/6s4J7ow+qgCcVwGyFtC/CLlGBNtlpHXCFtJ3sBQesWUPe4Smc0+5wTZI/j+0GBCa488Acx+xkk7waP8Gw15ZUjoE28lybLT1jyIkTACs4kq0g+JKAV5EMDGiHTzMEQNgyqK+bk424B1EF+7hXdY/u4immNJLopgh2TLxLe4vf5fwIMADtbDGrkqxb2AAAAAElFTkSuQmCC"); + background-color: transparent; + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + width: 1.333333333333333em; + height: 1.333333333333333em; + padding: 0; +} +.buttonsearch:hover, +.buttonsearch:focus { + background-color: transparent; + outline: none; + text-decoration: none; +} +.buttonarrowdown { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAALFJREFUeNrs1TEOgjAUxnFx5iDscgPvwXkYuYdXwBPgAYwrsxPGIAPPvKQkxLQO5UE0+X9J06Ff0186tImI7H45CUCAAAECBAgQIMA1s1+y+dkUpUXna/QGY0Z/q0QztCcJddyauG7UOdHA8XWXKT7khNO47rbArj5kIeQnTrubA0NIS9xioA9piTMB+pBWODPgHGmJ02H6UD/OeaZzerxc+eoAAgQIECBAgAD/AvgWYACm/8hEADYaXwAAAABJRU5ErkJggg=="); + width: 2.666666666666667em; + padding: 2.733333333333333em 0 0 0; +} +.buttonarrowrightwhite:after { + content: ''; + display: inline-block; + margin-left: 0.666666666666667em; + width: 0.466666666666667em; + height: 0.8em; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAMCAYAAACulacQAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFtJREFUeNpi+P///wEgPgzEokDMgIxBxMn/EHAVXQGIkIBKYCiAqUJXIIEsiVUBA5ojRJEUnGFiwAR/ofQvXLpQjMXpILxeOY8rEEAOegfER4DYAYhfI7sMIMAAiXvwy2upI1UAAAAASUVORK5CYII="); + background-size: contain; + background-repeat: no-repeat; +} +.label { + display: inline-block; + font-size: 0.8rem; + font-weight: 600; + color: #fff; + padding: 0.066666666666667em 0.666666666666667em; + position: relative; + margin-left: 0.833333333333333em; + height: 1.666666666666667em; + line-height: 1.666666666666667em; +} +.label:before { + content: ''; + position: absolute; + top: 0; + left: -0.833333333333333em; + width: 0; + height: 0; + border-top: 0.833333333333333em solid transparent; + border-bottom: 0.833333333333333em solid transparent; + border-right: 0.833333333333333em solid transparent; +} +.labellarge { + height: 2em; + line-height: 1.75em; + margin-left: 1em; +} +.labellarge:before { + left: -1em; + border-top-width: 1em; + border-bottom-width: 1em; + border-right-width: 1em; +} +.label[datalabeltype='live'] { + background-color: #e94a1b; + vertical-align: middle; + margin-top: -0.416666666666667em; +} +.label[datalabeltype='live']:before { + border-right-color: #e94a1b; +} +.label[datalabeltype='finished'] { + background-color: #8ab54e; + vertical-align: middle; + margin-top: -0.416666666666667em; +} +.label[datalabeltype='finished']:before { + border-right-color: #8ab54e; +} +.label[datalabeltype='progress'] { + background-color: #f0ab00; + vertical-align: middle; + margin-top: -0.416666666666667em; +} +.label[datalabeltype='progress']:before { + border-right-color: #f0ab00; +} +.label[datalabeltype='upcoming'] { + color: #333; + background-color: #eee; + vertical-align: middle; + margin-top: -0.416666666666667em; +} +.label[datalabeltype='upcoming']:before { + border-right-color: #eee; +} +.label[datalabeltype='new'], +.label[datalabeltype='updated'] { + background-color: #f0ab00; + text-transform: uppercase; + vertical-align: middle; +} +.label[datalabeltype='new']:before, +.label[datalabeltype='updated']:before { + border-right-color: #f0ab00; +} +.label[datalabeltype='ess'] { + background-color: #a71724; +} +.label[datalabeltype='ess']:before { + border-right-color: #a71724; +} +.label[datalabeltype='worldcup'] { + background-color: #c79242; +} +.label[datalabeltype='worldcup']:before { + border-right-color: #c79242; +} +.label[datalabeltype='bundesliga'] { + background-color: #005fa7; +} +.label[datalabeltype='bundesliga']:before { + border-right-color: #005fa7; +} +.morelink { + color: #008fcc; + text-transform: uppercase; + text-align: center; + font-size: 0.933333333333333em; + font-weight: 700; + padding: 1.866666666666667em; + display: block; + text-decoration: none; +} +.morelink:hover, +.morelink:focus { + text-decoration: underline; +} +.dummy { + font-size: 1rem; + min-height: 25em; + position: relative; +} +.dummy:after { + content: 'Dummy content'; + position: absolute; + top: 50%; + left: 50%; + font-weight: 700; + font-size: 6em; + color: #f7f7f7; + text-shadow: 1px 1px 0 #eee, 2px 2px 0 #eee, 3px 3px 0 #eee, 4px 4px 0 #eee, 5px 5px 0 #eee, 6px 6px 0 #eee, 7px 7px 0 #eee; + margin: -100px 0 0 -350px; +} +.mainsection { + padding-top: 5.333333333333333em; + padding-bottom: 5.333333333333333em; +} +.mainsection_header { + text-align: center; + margin-bottom: 3.666666666666667em; +} +.mainsection_header p { + margin-top: 0; + font-size: 1.2em; +} +.mainsection_header_title { + margin: 0; + font-size: 2.4em; + text-align: center; +} +.mainsection_navigation { + text-align: center; +} +.lightbox { + font-size: 1rem; +} +.lightbox_overlay { + display: none; + font-size: 1rem; + background: rgba(0,0,0,0.75); + width: 100%; + height: 100%; + position: fixed; + left: 0; + top: 0; + z-index: 10; + -webkit-transition: opacity 0.3s ease; + -moz-transition: opacity 0.3s ease; + transition: opacity 0.3s ease; +} +.lightbox_content { + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + max-width: 90%; + max-height: 90%; + overflow: hidden; +} +.lightbox_close { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHVJREFUeNqsk4EJACEIReWawY2bMLh5LME7JNQEE4So/x9pCUTUVvaVuBKSieJpIAuOkYSgaDn6t/EmIdrMHtQHJ4g2/5pd4EFM8w7whK7ZAliQEZWWaVbY3AeqcbuEUhMj4fEZ8XTV6CNlzC7kyjCVxnkKMABEXRUAnXsRRQAAAABJRU5ErkJggg=="); + background-repeat: no-repeat; + background-position: center center; + width: 1.333333333333333em; + height: 0; + padding-top: 1.333333333333333em; + position: absolute; + top: 1.333333333333333em; + right: 1.333333333333333em; + cursor: pointer; + z-index: 2; +} +.stickyheader { + background: inherit; +} +.slideto { + background: inherit; + border-bottom: 0.266666666666667em solid transparent; +} +.slidetonav { + background: inherit; +} +.slidetoactive { + border-bottom-color: #f0ab00; + text-decoration: none; + color: #dedede; +} +.slidetoactive:hover, +.slidetoactive:focus { + border-bottom-color: #d89a00; +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/media.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/media.gss new file mode 100644 index 00000000000..611ff15f214 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/media.gss @@ -0,0 +1,450 @@ +/** + * The grid styles + */ +.grid, +.stackenblochen { + display: block; + width: 100%; + max-width: 80rem; + list-style: none; + margin: 0 auto; + zoom: 1; +} +.grid:before, +.stackenblochen:before, +.grid:after, +.stackenblochen:after { + content: ""; + display: table; +} +.grid:after, +.stackenblochen:after { + clear: both; +} +.grid *, +.stackenblochen * { + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.grid .grid, +.stackenblochen .grid, +.grid .stackenblochen, +.stackenblochen .stackenblochen { + width: auto; + max-width: none; + margin-left: -1rem; + margin-right: -1rem; +} +.column, +.columns { + float: left; + padding: 0 1rem; + position: relative; + width: 100%; +} +.column img, +.columns img { + max-width: 100%; + height: auto; + ms-interpolation-mode: bicubic; +} +.small1 { + width: 8.3333333333333%; +} +.small2 { + width: 16.6666666666667%; +} +.small3 { + width: 25%; +} +.small4 { + width: 33.3333333333333%; +} +.small5 { + width: 41.6666666666667%; +} +.small6 { + width: 50%; +} +.small7 { + width: 58.3333333333333%; +} +.small8 { + width: 66.6666666666667%; +} +.small9 { + width: 75%; +} +.small10 { + width: 83.3333333333333%; +} +.small11 { + width: 91.6666666666667%; +} +.small12 { + width: 100%; +} +.smalloffset0 { + margin-left: 0% !important; +} +.smalloffset1 { + margin-left: 8.3333333333333% !important; +} +.smalloffset2 { + margin-left: 16.6666666666667% !important; +} +.smalloffset3 { + margin-left: 25% !important; +} +.smalloffset4 { + margin-left: 33.3333333333333% !important; +} +.smalloffset5 { + margin-left: 41.6666666666667% !important; +} +.smalloffset6 { + margin-left: 50% !important; +} +.smalloffset7 { + margin-left: 58.3333333333333% !important; +} +.smalloffset8 { + margin-left: 66.6666666666667% !important; +} +.smalloffset9 { + margin-left: 75% !important; +} +.smalloffset10 { + margin-left: 83.3333333333333% !important; +} +.smalloffset11 { + margin-left: 91.6666666666667% !important; +} +.showonsmall { + display: inherit !important; +} +.hideonsmall { + display: none !important; +} +table.showonsmall { + display: table !important; +} +tr.showonsmall { + display: table-row !important; +} +td.showonsmall, +th.showonsmall { + display: table-cell !important; +} +.showonmedium { + display: none !important; +} +.hideonmedium { + display: inherit !important; +} +table.hideonmedium { + display: table !important; +} +tr.hideonmedium { + display: table-row !important; +} +td.hideonmedium, +th.hideonmedium { + display: table-cell !important; +} +.showonlarge { + display: none !important; +} +.hideonlarge { + display: inherit !important; +} +table.hideonlarge { + display: table !important; +} +tr.hideonlarge { + display: table-row !important; +} +td.hideonlarge, +th.hideonlarge { + display: table-cell !important; +} +.column.smallcentered, +.columns.smallcentered { + margin-left: auto; + margin-right: auto; + float: none; +} +.column.smalluncentered, +.columns.smalluncentered { + margin-left: 0; + margin-right: 0; + float: left !important; +} +@media (min-width: 45em) { + .medium1 { + width: 8.3333333333333%; + } + .medium2 { + width: 16.6666666666667%; + } + .medium3 { + width: 25%; + } + .medium4 { + width: 33.3333333333333%; + } + .medium5 { + width: 41.6666666666667%; + } + .medium6 { + width: 50%; + } + .medium7 { + width: 58.3333333333333%; + } + .medium8 { + width: 66.6666666666667%; + } + .medium9 { + width: 75%; + } + .medium10 { + width: 83.3333333333333%; + } + .medium11 { + width: 91.6666666666667%; + } + .medium12 { + width: 100%; + } + .mediumoffset0 { + margin-left: 0% !important; + } + .mediumoffset1 { + margin-left: 8.3333333333333% !important; + } + .mediumoffset2 { + margin-left: 16.6666666666667% !important; + } + .mediumoffset3 { + margin-left: 25% !important; + } + .mediumoffset4 { + margin-left: 33.3333333333333% !important; + } + .mediumoffset5 { + margin-left: 41.6666666666667% !important; + } + .mediumoffset6 { + margin-left: 50% !important; + } + .mediumoffset7 { + margin-left: 58.3333333333333% !important; + } + .mediumoffset8 { + margin-left: 66.6666666666667% !important; + } + .mediumoffset9 { + margin-left: 75% !important; + } + .mediumoffset10 { + margin-left: 83.3333333333333% !important; + } + .mediumoffset11 { + margin-left: 91.6666666666667% !important; + } + .showonsmall { + display: none !important; + } + .hideonsmall { + display: inherit !important; + } + table.hideonsmall { + display: table !important; + } + tr.hideonsmall { + display: table-row !important; + } + td.hideonsmall, + th.hideonsmall { + display: table-cell !important; + } + .showonmedium { + display: inherit !important; + } + .hideonmedium { + display: none !important; + } + table.showonmedium { + display: table !important; + } + tr.showonmedium { + display: table-row !important; + } + td.showonmedium, + th.showonmedium { + display: table-cell !important; + } + .showonlarge { + display: none !important; + } + .hideonlarge { + display: inherit !important; + } + table.hideonlarge { + display: table !important; + } + tr.hideonlarge { + display: table-row !important; + } + td.hideonlarge, + th.hideonlarge { + display: table-cell !important; + } + .column.mediumcentered, + .columns.mediumcentered { + margin-left: auto; + margin-right: auto; + float: none; + } + .column.mediumuncentered, + .columns.mediumuncentered { + margin-left: 0; + margin-right: 0; + float: left !important; + } +} +@media (min-width: 61em) { + .large1 { + width: 8.3333333333333%; + } + .large2 { + width: 16.6666666666667%; + } + .large3 { + width: 25%; + } + .large4 { + width: 33.3333333333333%; + } + .large5 { + width: 41.6666666666667%; + } + .large6 { + width: 50%; + } + .large7 { + width: 58.3333333333333%; + } + .large8 { + width: 66.6666666666667%; + } + .large9 { + width: 75%; + } + .large10 { + width: 83.3333333333333%; + } + .large11 { + width: 91.6666666666667%; + } + .large12 { + width: 100%; + } + .largeoffset0 { + margin-left: 0% !important; + } + .largeoffset1 { + margin-left: 8.3333333333333% !important; + } + .largeoffset2 { + margin-left: 16.6666666666667% !important; + } + .largeoffset3 { + margin-left: 25% !important; + } + .largeoffset4 { + margin-left: 33.3333333333333% !important; + } + .largeoffset5 { + margin-left: 41.6666666666667% !important; + } + .largeoffset6 { + margin-left: 50% !important; + } + .largeoffset7 { + margin-left: 58.3333333333333% !important; + } + .largeoffset8 { + margin-left: 66.6666666666667% !important; + } + .largeoffset9 { + margin-left: 75% !important; + } + .largeoffset10 { + margin-left: 83.3333333333333% !important; + } + .largeoffset11 { + margin-left: 91.6666666666667% !important; + } + .showonsmall { + display: none !important; + } + .hideonsmall { + display: inherit !important; + } + table.hideonsmall { + display: table !important; + } + tr.hideonsmall { + display: table-row !important; + } + td.hideonsmall, + th.hideonsmall { + display: table-cell !important; + } + .showonmedium { + display: none !important; + } + .hideonmedium { + display: inherit !important; + } + table.hideonmedium { + display: table !important; + } + tr.hideonmedium { + display: table-row !important; + } + td.hideonmedium, + th.hideonmedium { + display: table-cell !important; + } + .showonlarge { + display: inherit !important; + } + .hideonlarge { + display: none !important; + } + table.showonlarge { + display: table !important; + } + tr.showonlarge { + display: table-row !important; + } + td.showonlarge, + th.showonlarge { + display: table-cell !important; + } + .column.largecentered, + .columns.largecentered { + margin-left: auto; + margin-right: auto; + float: none; + } + .column.largeuncentered, + .columns.largeuncentered { + margin-left: 0; + margin-right: 0; + float: left !important; + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.java new file mode 100644 index 00000000000..bd6f890cc59 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.java @@ -0,0 +1,53 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiChild; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; + +public class ComponentShowCase extends Composite { + + private static ComponentShowCaseUiBinder uiBinder = GWT.create(ComponentShowCaseUiBinder.class); + + interface ComponentShowCaseUiBinder extends UiBinder { + } + + @UiField + DivElement componentNameUi; + + @UiField + SimplePanel componentDescriptionUi; + + @UiField + SimplePanel componentPanelUi; + + private String name; + + public ComponentShowCase() { + initWidget(uiBinder.createAndBindUi(this)); + } + + public void setName(String name) { + this.name = name; + componentNameUi.setInnerText(name); + } + + @UiChild + public void addDescription(Widget description) { + componentDescriptionUi.setWidget(description); + } + + @UiChild + public void addComponent(Widget w) { + componentPanelUi.setWidget(w); + } + + public String getName() { + return this.name; + } + +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml new file mode 100644 index 00000000000..cfad5712d50 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml @@ -0,0 +1,30 @@ + + + + .outerpanel { + margin: 20px; + } + .componentName { + font-size: 20px; + font-weight: bold; + magin-bottom: 10px; + + } + .componentDescription { + margin-bottom: 30px; + border-bottom: 3px solid black; + padding: 20px; + width: 100%; + + } + + +
+ + + + + +
+
\ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.java new file mode 100644 index 00000000000..7ab3fd01d6c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.java @@ -0,0 +1,30 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; + +public class MainPage extends Composite { + + private static MainPageUiBinder uiBinder = GWT.create(MainPageUiBinder.class); + + interface MainPageUiBinder extends UiBinder { + } + + @UiField(provided = true) + SimplePanel contentUi; + + @UiField(provided = true) + ShowcaseStackLayoutPanel stackLayoutPanelUi; + + public MainPage() { + contentUi = new SimplePanel(); + stackLayoutPanelUi = new ShowcaseStackLayoutPanel(Unit.EM); + stackLayoutPanelUi.setTarget(contentUi); + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml new file mode 100644 index 00000000000..6a61941b82d --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml @@ -0,0 +1,33 @@ + + + + + + + Styleguide + + + tbd. + + + + + Components + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.java new file mode 100644 index 00000000000..d6407623138 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.java @@ -0,0 +1,50 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiChild; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; + +public class Section extends Composite { + private static ShowCaseSectionUiBinder uiBinder = GWT.create(ShowCaseSectionUiBinder.class); + + interface ShowCaseSectionUiBinder extends UiBinder { + } + + private AcceptsOneWidget target; + + @UiField + FlowPanel panelUi; + + public Section() { + initWidget(uiBinder.createAndBindUi(this)); + } + + public void setTargetContentArea(AcceptsOneWidget target) { + this.target = target; + } + + @UiChild + public void addItem(final Widget widget, String name) { + SimplePanel p = new SimplePanel(); + Anchor showCaseItem = new Anchor(name); + p.setWidget(showCaseItem); + showCaseItem.addClickHandler(new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + target.setWidget(widget); + } + }); + + panelUi.add(p); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.ui.xml new file mode 100644 index 00000000000..7f8562acc98 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/Section.ui.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowcaseStackLayoutPanel.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowcaseStackLayoutPanel.java new file mode 100644 index 00000000000..4889e6aa5ec --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowcaseStackLayoutPanel.java @@ -0,0 +1,27 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.StackLayoutPanel; +import com.google.gwt.user.client.ui.Widget; + +public class ShowcaseStackLayoutPanel extends StackLayoutPanel { + private SimplePanel target; + + public ShowcaseStackLayoutPanel(Unit unit) { + super(unit); + } + + public void setTarget(SimplePanel target) { + this.target = target; + } + + @Override + public void add(Widget widget, String header, boolean asHtml, double headerSize) { + if (widget instanceof Section) { + Section section = (Section) widget; + section.setTargetContentArea(target); + } + super.add(widget, header, asHtml, headerSize); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.java new file mode 100644 index 00000000000..a4b84a55a22 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.java @@ -0,0 +1,131 @@ +package com.sap.sse.gwt.theme.client.showcase.recentevent; + +import java.util.Date; + +import com.google.gwt.core.client.GWT; +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.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.recentevent.RecentEvent; +import com.sap.sse.gwt.theme.client.component.recentevent.RecentEventData; + +public class RecentEventShowcase extends Composite { + + private static RecentEventShowcaseUiBinder uiBinder = GWT.create(RecentEventShowcaseUiBinder.class); + + interface RecentEventShowcaseUiBinder extends UiBinder { + } + + @UiField(provided = true) + RecentEvent finishedItem; + + @UiField(provided = true) + RecentEvent liveItem; + + public RecentEventShowcase() { + finishedItem = new RecentEvent(new RecentEventData() { + + @Override + public boolean isLive() { + return false; + } + + @Override + public String getVenue() { + return "Venue1"; + } + + @Override + public String getLocation() { + return "Loc1"; + } + + @Override + public Date getEventStart() { + return new Date(); + } + + @Override + public String getEventName() { + return "SomeName"; + } + + @Override + public String getEventImageUrl() { + + return "http://demotivators.despair.com/demotivational/mistakesdemotivator.jpg"; + } + + @Override + public Date getEventEnd() { + return new Date(); + } + + @Override + public Command getCommand() { + return new Command() { + + @Override + public void execute() { + Window.alert("done"); + } + }; + } + }); + + liveItem = new RecentEvent(new RecentEventData() { + + @Override + public boolean isLive() { + return true; + } + + @Override + public String getVenue() { + return "Venue2"; + } + + @Override + public String getLocation() { + return "Loc2"; + } + + @Override + public Date getEventStart() { + return new Date(); + } + + @Override + public String getEventName() { + return "SomeName"; + } + + @Override + public String getEventImageUrl() { + + return "http://demotivators.despair.com/demotivational/mistakesdemotivator.jpg"; + } + + @Override + public Date getEventEnd() { + return new Date(); + } + + @Override + public Command getCommand() { + return new Command() { + + @Override + public void execute() { + Window.alert("done"); + } + }; + } + }); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.ui.xml new file mode 100644 index 00000000000..0e292882202 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.ui.xml @@ -0,0 +1,35 @@ + + + + .holder { + width: 400px; + display: block; + magin: 10px; + + } + + + + + This is a very cool recent event widget. +

Import: xmlns:ubi="urn:import:com.sap.sse.gwt.theme.client.sailing"

+
+
+ + +

FINISHED:

+
+ +
+ +

LIVE:

+
+ +
+
+
+
+
\ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.java new file mode 100644 index 00000000000..8e16cf7a833 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.java @@ -0,0 +1,25 @@ +package com.sap.sse.gwt.theme.client.showcase.sapheader; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.sapheader.SAPHeader; + +public class SAPHeaderShowcase extends Composite { + + private static SAPHeaderShowcaseUiBinder uiBinder = GWT.create(SAPHeaderShowcaseUiBinder.class); + + interface SAPHeaderShowcaseUiBinder extends UiBinder { + } + + @UiField(provided = true) + SAPHeader sapHeader; + + public SAPHeaderShowcase() { + sapHeader = new SAPHeader("Sailing Analytics", "Demo page", false); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml new file mode 100644 index 00000000000..108308c4951 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml @@ -0,0 +1,29 @@ + + + + .holder { + width: 400px; + display: block; + magin: 10px; + + } + + + + + A simple header with SAP logo and title. +

Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.sapheader"

+
+
+ + +
+ +
+
+
+
+
\ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/styleguide/typeface/typeface.html b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/styleguide/typeface/typeface.html new file mode 100644 index 00000000000..5e580d7c2be --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/styleguide/typeface/typeface.html @@ -0,0 +1,24 @@ +
+
Typeface Open Sans
+
+
+
REGULAR
</> +
+
Grumpy wizards make toxic brew for the evil queen and jack.
+
Code und sooo
+
+
+
+
SEMIBOLD
</> +
+
Grumpy wizards make toxic brew for the evil queen and jack.
+
Code und sooo
+
+
+
+
BOLD
</> +
+
Grumpy wizards make toxic brew for the evil queen and jack.
+
Code und sooo
+
+
diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/util/FullscreenUtil.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/util/FullscreenUtil.java new file mode 100644 index 00000000000..27ff25bebd3 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/util/FullscreenUtil.java @@ -0,0 +1,27 @@ +package com.sap.sse.gwt.theme.client.util; + +/** + * A utility class to set the browser to fullscreen mode + * @author c5163874 (Frank) + * + */ +public class FullscreenUtil { + + public static void requestFullscreen() { + nativeRequestFullScreen(); + }; + + private static native void nativeRequestFullScreen() /*-{ + var element = $doc.documentElement; + if(element.requestFullscreen) { + element.requestFullscreen(); + } else if(element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if(element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if(element.msRequestFullscreen) { + element.msRequestFullscreen(); + } + }-*/; + +} From d172ed8e50ff806394cb51e127c29420709d7895 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 4 Feb 2015 18:28:44 +0100 Subject: [PATCH 018/264] added simple footer to component lib as sample --- java/com.sap.sse.gwt.theme/.classpath | 1 - java/com.sap.sse.gwt.theme/build.properties | 3 +- .../component/recentevent/RecentEvent.css | 47 ------- .../component/recentevent/RecentEvent.java | 69 --------- .../component/recentevent/RecentEvent.ui.xml | 22 --- .../recentevent/RecentEventData.java | 16 --- .../recentevent/RecentEventResources.java | 25 ---- .../recentevent/default_event_photo.jpg | Bin 28976 -> 0 bytes .../client/component/sapfooter/SAPFooter.gss | 55 ++++++++ .../client/component/sapfooter/SAPFooter.java | 68 +++++++++ .../component/sapfooter/SAPFooter.ui.xml | 33 +++++ .../sapfooter/SAPFooterResources.java | 21 +++ .../component/sapheader/SAPHeaderData.java | 5 - .../client/resources/i18n/TextMessages.java | 7 + .../resources/i18n/TextMessages.properties | 7 +- .../resources/i18n/TextMessages_de.properties | 8 +- .../gwt/theme/client/showcase/MainPage.ui.xml | 20 +-- .../recentevent/RecentEventShowcase.java | 131 ------------------ .../recentevent/RecentEventShowcase.ui.xml | 35 ----- .../showcase/sapfooter/SAPFooterShowcase.java | 25 ++++ .../sapfooter/SAPFooterShowcase.ui.xml | 26 ++++ .../sapheader/SAPHeaderShowcase.ui.xml | 13 +- 22 files changed, 264 insertions(+), 373 deletions(-) delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.css delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.java delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.ui.xml delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventData.java delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventResources.java delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/default_event_photo.jpg create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.gss create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooterResources.java delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderData.java delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.java delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.ui.xml diff --git a/java/com.sap.sse.gwt.theme/.classpath b/java/com.sap.sse.gwt.theme/.classpath index b983a4da697..ffa9f4e615c 100644 --- a/java/com.sap.sse.gwt.theme/.classpath +++ b/java/com.sap.sse.gwt.theme/.classpath @@ -1,7 +1,6 @@ - diff --git a/java/com.sap.sse.gwt.theme/build.properties b/java/com.sap.sse.gwt.theme/build.properties index 95d385246a8..41365161d8b 100644 --- a/java/com.sap.sse.gwt.theme/build.properties +++ b/java/com.sap.sse.gwt.theme/build.properties @@ -1,5 +1,4 @@ -source.WEB-INF/classes/ = src/main/java,\ - src/main/resources +source.WEB-INF/classes/ = src/main/java output.WEB-INF/classes/ = bin bin.includes = META-INF/,\ WEB-INF/classes/,\ diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.css b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.css deleted file mode 100644 index 519353979c9..00000000000 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.css +++ /dev/null @@ -1,47 +0,0 @@ -.eventteasercontainer { - font-size: 1rem; - text-align: center; - width: 100%; - height: 24em; -} -.eventteaser { - font-size: 1rem; - display: inline-block; - margin-bottom: 3em; - text-align: left; - max-width: 100%; - width: 100%; -} -.eventteaser:hover { - text-decoration: none; -} -.eventteaser:hover .eventteaser_name { - text-decoration: underline; -} -.eventteaser:hover .eventteaser_image { - opacity: 0.9; -} -.eventteaser_image { - position: relative; - display: inline-block; - max-width: 100%; - width: 100%; - height: 240px; - background-position: center; - background-size: cover; - background-repeat: no-repeat; -} -.eventteaser_series { - position: absolute; - bottom: 0; - right: 0; -} -.eventteaser_name { - font-size: 1.466666666666667em; - font-weight: bold; - color: #008fcc; - margin-top: 0.333333333333333em; -} -.eventteaser_location { - color: #222; -} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.java deleted file mode 100644 index b5fde43544c..00000000000 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.sap.sse.gwt.theme.client.component.recentevent; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.SpanElement; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.Anchor; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.Widget; - -public class RecentEvent extends Composite { - - @UiField - SpanElement eventNameUi; - - @UiField - SpanElement venueNameUi; - - @UiField - SpanElement eventDateUi; - - @UiField - Anchor eventOverviewUi; - - @UiField - DivElement eventImageContainerUi; - - @UiField - DivElement isLiveUi; - - interface RecentEventUiBinder extends UiBinder { - } - - private static RecentEventUiBinder uiBinder = GWT.create(RecentEventUiBinder.class); - private Command eventClickAction; - - public RecentEvent(RecentEventData data) { - - this.eventClickAction = data.getCommand(); - RecentEventResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - - eventNameUi.setInnerText(data.getEventName()); - venueNameUi.setInnerText(data.getVenue()); - eventDateUi.setInnerText(data.getEventStart().toString().substring(10) + " " - + data.getEventEnd().toString().substring(10)); - - final StringBuilder thumbnailUrlBuilder = new StringBuilder("url('").append(data.getEventImageUrl()).append( - "')"); - GWT.log(thumbnailUrlBuilder.toString()); - eventImageContainerUi.getStyle().setBackgroundImage(thumbnailUrlBuilder.toString()); - if (data.isLive()) - isLiveUi.getStyle().setDisplay(Display.BLOCK); - else - isLiveUi.getStyle().setDisplay(Display.NONE); - } - - @UiHandler("eventOverviewUi") - public void goToEventOverview(ClickEvent e) { - eventClickAction.execute(); - e.preventDefault(); - } - -} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.ui.xml deleted file mode 100644 index 92b0202e911..00000000000 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEvent.ui.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - -
- -
-
-
-
-
- ,  -
-
-
-
-
diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventData.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventData.java deleted file mode 100644 index b3788eaab89..00000000000 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventData.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.sap.sse.gwt.theme.client.component.recentevent; - -import java.util.Date; - -import com.google.gwt.user.client.Command; - -public interface RecentEventData { - String getEventImageUrl(); - String getVenue(); - String getLocation(); - Date getEventStart(); - Date getEventEnd(); - Command getCommand(); - String getEventName(); - boolean isLive(); -} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventResources.java deleted file mode 100644 index 6d2229572a1..00000000000 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/RecentEventResources.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.sap.sse.gwt.theme.client.component.recentevent; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.CssResource; -import com.google.gwt.resources.client.ImageResource; - -public interface RecentEventResources extends ClientBundle { - public static final RecentEventResources INSTANCE = GWT.create(RecentEventResources.class); - - @Source("RecentEvent.css") - LocalCss css(); - - public interface LocalCss extends CssResource { - String eventteaser(); - String eventteaser_name(); - String eventteaser_image(); - String eventteaser_series(); - String eventteaser_location(); - String eventteasercontainer(); - } - - @Source("default_event_photo.jpg") - ImageResource defaultEventPhotoImage(); -} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/default_event_photo.jpg b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/recentevent/default_event_photo.jpg deleted file mode 100644 index 03f41052084a0cb160d540dc85c7ab3f9459db39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28976 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 diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.gss new file mode 100644 index 00000000000..05a39fd654a --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.gss @@ -0,0 +1,55 @@ +/** + * Website footer styling + */ +.sitefooter { + font-size: 1rem; + background: #333; + border-top: 5px solid #fff; + color: #fff; + line-height: 5em; + width: 100%; + padding: 0 1.333333333333333em; +} +.sitefooter_copyright { + font-weight: 600; + margin-right: 2em; +} +@media (min-width: 45em) { + .sitefooter_copyright { + float: left; + } +} +.sitefooter_links { + text-align: left; + font-size: 1em; + font-weight: 600; + text-transform: uppercase; +} +.sitefooter_links_link { + display: block; + color: #fff; + text-decoration: none; + margin-right: 2em; +} +.sitefooter_links_link:last-child { + margin-right: 0; +} +@media (min-width: 45em) { + .sitefooter_links_link { + display: inline-block; + } +} +.sitefooter_language { + text-align: left; +} +@media (min-width: 45em) { + .sitefooter_language { + text-align: right; + } +} +.sitefooter_language_link { + color: #f0ab00; + display: inline-block; + cursor: pointer; + margin-left: 0.666666666666667em; +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.java new file mode 100644 index 00000000000..88fc59bed9c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.java @@ -0,0 +1,68 @@ +package com.sap.sse.gwt.theme.client.component.sapfooter; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.http.client.UrlBuilder; +import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.Window.Location; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class SAPFooter extends Composite { + private static SAPFooterPanelUiBinder uiBinder = GWT.create(SAPFooterPanelUiBinder.class); + + interface SAPFooterPanelUiBinder extends UiBinder { + } + + @UiField Anchor changeLanguageLink; + @UiField DivElement languageSelectionDiv; + + private final Map localeAndLanguages; + + public SAPFooter() { + localeAndLanguages = new HashMap(); + localeAndLanguages.put("default", "English"); + localeAndLanguages.put("de", "Deutsch"); + + SAPFooterResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + + updateUI(); + } + + @UiHandler("changeLanguageLink") + public void changeLanguage(ClickEvent e) { + String selectedLanguage = changeLanguageLink.getText(); + String selectedLocale = null; + for (Entry entry: localeAndLanguages.entrySet()) { + if (entry.getValue().equals(selectedLanguage)) { + selectedLocale = entry.getKey(); + } + } + if(selectedLocale != null) { + UrlBuilder builder = Location.createUrlBuilder().setParameter("locale", selectedLocale); + Window.Location.replace(builder.buildString()); + } + } + + private void updateUI() { + LocaleInfo currentLocale = LocaleInfo.getCurrentLocale(); + HashMap languagesToSelectFrom = new HashMap(localeAndLanguages); + languagesToSelectFrom.remove(currentLocale.getLocaleName()); + + if(languagesToSelectFrom.size() == 1) { + changeLanguageLink.setText(languagesToSelectFrom.values().iterator().next()); + } + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.ui.xml new file mode 100644 index 00000000000..e91510e8856 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.ui.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooterResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooterResources.java new file mode 100644 index 00000000000..0009fba40a4 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooterResources.java @@ -0,0 +1,21 @@ +package com.sap.sse.gwt.theme.client.component.sapfooter; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface SAPFooterResources extends ClientBundle { + public static final SAPFooterResources INSTANCE = GWT.create(SAPFooterResources.class); + + @Source("com/sap/sse/gwt/theme/client/component/sapfooter/SAPFooter.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String sitefooter(); + String sitefooter_copyright(); + String sitefooter_links(); + String sitefooter_links_link(); + String sitefooter_language(); + String sitefooter_language_link(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderData.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderData.java deleted file mode 100644 index 16804993bd9..00000000000 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderData.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.sap.sse.gwt.theme.client.component.sapheader; - -public interface SAPHeaderData { - -} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java index 3b9b9964efc..32cf93c7b04 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.java @@ -7,4 +7,11 @@ public interface TextMessages extends Messages { public static final TextMessages INSTANCE = GWT.create(TextMessages.class); String chooseALanguage(); + + String footerCopyright(); + String footerLanguage(); + String footerLegal(); + String footerPrivacy(); + String footerFeedback(); + } diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties index 3bb701bcae5..b5aa42979c2 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages.properties @@ -1 +1,6 @@ -chooseALanguage=Choose a language +chooseALanguage = Choose a language +footerCopyright = \u00A9 2014 SAP Sailing Analytics +footerFeedback = Feedback +footerLanguage = SAP Sailing offered in +footerLegal = Legal Disclosure +footerPrivacy = Privacy diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties index c638bf7471d..6b3cbc592e8 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/resources/i18n/TextMessages_de.properties @@ -1 +1,7 @@ -chooseALanguage=Wähle eine Sprache +chooseALanguage = W\u00E4hle eine Sprache +footerCopyright = \u00A9 2014 SAP Sailing Analytics +footerFeedback = Feedback +footerLanguage = SAP Sailing in +footerLegal = Impressum +footerPrivacy = Datenschutz + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml index 6a61941b82d..f14b9af839d 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml @@ -5,22 +5,22 @@ - - Styleguide - - - tbd. - - - Components - - + + + + + + + Styleguide + + + tbd. diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.java deleted file mode 100644 index a4b84a55a22..00000000000 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.sap.sse.gwt.theme.client.showcase.recentevent; - -import java.util.Date; - -import com.google.gwt.core.client.GWT; -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.Composite; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sse.gwt.theme.client.component.recentevent.RecentEvent; -import com.sap.sse.gwt.theme.client.component.recentevent.RecentEventData; - -public class RecentEventShowcase extends Composite { - - private static RecentEventShowcaseUiBinder uiBinder = GWT.create(RecentEventShowcaseUiBinder.class); - - interface RecentEventShowcaseUiBinder extends UiBinder { - } - - @UiField(provided = true) - RecentEvent finishedItem; - - @UiField(provided = true) - RecentEvent liveItem; - - public RecentEventShowcase() { - finishedItem = new RecentEvent(new RecentEventData() { - - @Override - public boolean isLive() { - return false; - } - - @Override - public String getVenue() { - return "Venue1"; - } - - @Override - public String getLocation() { - return "Loc1"; - } - - @Override - public Date getEventStart() { - return new Date(); - } - - @Override - public String getEventName() { - return "SomeName"; - } - - @Override - public String getEventImageUrl() { - - return "http://demotivators.despair.com/demotivational/mistakesdemotivator.jpg"; - } - - @Override - public Date getEventEnd() { - return new Date(); - } - - @Override - public Command getCommand() { - return new Command() { - - @Override - public void execute() { - Window.alert("done"); - } - }; - } - }); - - liveItem = new RecentEvent(new RecentEventData() { - - @Override - public boolean isLive() { - return true; - } - - @Override - public String getVenue() { - return "Venue2"; - } - - @Override - public String getLocation() { - return "Loc2"; - } - - @Override - public Date getEventStart() { - return new Date(); - } - - @Override - public String getEventName() { - return "SomeName"; - } - - @Override - public String getEventImageUrl() { - - return "http://demotivators.despair.com/demotivational/mistakesdemotivator.jpg"; - } - - @Override - public Date getEventEnd() { - return new Date(); - } - - @Override - public Command getCommand() { - return new Command() { - - @Override - public void execute() { - Window.alert("done"); - } - }; - } - }); - - initWidget(uiBinder.createAndBindUi(this)); - } -} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.ui.xml deleted file mode 100644 index 0e292882202..00000000000 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/recentevent/RecentEventShowcase.ui.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - .holder { - width: 400px; - display: block; - magin: 10px; - - } - - - - - This is a very cool recent event widget. -

Import: xmlns:ubi="urn:import:com.sap.sse.gwt.theme.client.sailing"

-
-
- - -

FINISHED:

-
- -
- -

LIVE:

-
- -
-
-
-
-
\ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.java new file mode 100644 index 00000000000..3d4f769b33b --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.java @@ -0,0 +1,25 @@ +package com.sap.sse.gwt.theme.client.showcase.sapfooter; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.sapfooter.SAPFooter; + +public class SAPFooterShowcase extends Composite { + + private static SAPHeaderShowcaseUiBinder uiBinder = GWT.create(SAPHeaderShowcaseUiBinder.class); + + interface SAPHeaderShowcaseUiBinder extends UiBinder { + } + + @UiField(provided = true) + SAPFooter sapFooter; + + public SAPFooterShowcase() { + sapFooter = new SAPFooter(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.ui.xml new file mode 100644 index 00000000000..fd55cb46f05 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.ui.xml @@ -0,0 +1,26 @@ + + + + .showcase_content { + width: 100%; + display: block; + margin: 10px; + } + + + + + A SAP footer. +

Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.sapfooter"

+
+
+ + + + + +
+
\ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml index 108308c4951..cd72d089a03 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml @@ -4,11 +4,10 @@ xmlns:ux="urn:import:com.sap.sse.gwt.theme.client.showcase" xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.sapheader"> - .holder { - width: 400px; + .showcase_content { + width: 100%; display: block; - magin: 10px; - + margin: 10px; } @@ -19,10 +18,8 @@ - -
- -
+ +
From 0e4775d51f8e51f3a7a3db2820d6c4655de46d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bro=C3=9F?= Date: Wed, 4 Feb 2015 22:36:48 +0100 Subject: [PATCH 019/264] Add email field to Competitor --- .../domain/common/dto/CompetitorDTO.java | 2 ++ .../domain/common/dto/CompetitorDTOImpl.java | 18 ++++++++++++++++- .../common/dto/PreviousCompetitorDTOImpl.java | 6 ++++++ .../test/AbstractGPSFixStoreTest.java | 2 +- .../impl/CreateAndTrackWithRaceLogTest.java | 4 ++-- .../test/impl/TrackedRaceLoadsFixesTest.java | 2 +- .../sap/sailing/domain/base/Competitor.java | 4 ++++ .../domain/base/CompetitorFactory.java | 2 +- .../sailing/domain/base/CompetitorStore.java | 2 +- .../domain/base/impl/CompetitorImpl.java | 16 ++++++++++++++- .../domain/base/impl/DynamicCompetitor.java | 1 + .../base/impl/SharedDomainFactoryImpl.java | 4 ++-- .../impl/TransientCompetitorStoreImpl.java | 9 +++++---- .../impl/DomainFactoryImpl.java | 4 ++-- .../test/LeaderboardDTODiffingTest.java | 8 ++++---- .../domain/test/AbstractLeaderboardTest.java | 2 +- .../impl/DomainFactoryImpl.java | 2 +- .../base/impl/DynamicCompetitorStore.java | 2 +- .../DummyMarkPassingWithTimePointOnly.java | 10 ++++++++++ .../ui/adminconsole/CompetitorEditDialog.java | 13 +++++++++--- .../sailing/gwt/ui/client/StringMessages.java | 2 ++ .../gwt/ui/client/StringMessages.properties | 4 +++- .../ui/client/StringMessages_de.properties | 4 +++- .../ui/client/StringMessages_ru.properties | 4 +++- .../gwt/ui/server/SailingServiceImpl.java | 4 ++-- .../test/StoreAndLoadRaceLogEventsTest.java | 4 ++-- .../mongodb/test/StoreGPSFixTrackTest.java | 2 +- .../TestStoringAndRetrievingLeaderboards.java | 2 +- ...ingAndRetrievingRaceLogInLeaderboards.java | 4 ++-- ...tStoringAndRetrievingRaceLogInRegatta.java | 4 ++-- .../impl/CompetitorJsonDeserializer.java | 5 ++++- .../impl/CompetitorJsonSerializer.java | 2 ++ .../racelog/RaceLogEventSerializerTest.java | 2 +- .../test/jaxrs/CompetitorsResourceTest.java | 2 +- ...LeaderboardsResourceCheckinAndOutTest.java | 20 ++++++++++--------- .../gateway/test/jaxrs/TeamImageTest.java | 2 +- .../jaxrs/api/CompetitorsResource.java | 2 +- .../test/CompetitorReplicationTest.java | 6 +++--- .../TrackedRaceContentsReplicationTest.java | 2 +- .../server/test/CompetitorStoreTest.java | 14 ++++++------- .../server/test/LeaderboardStorageTest.java | 2 +- .../server/test/MasterDataImportTest.java | 6 +++--- .../server/test/WindByRaceLogTest.java | 2 +- .../impl/PersistentCompetitorStore.java | 4 ++-- .../server/impl/RacingEventServiceImpl.java | 4 ++-- .../UpdateCompetitor.java | 6 ++++-- .../structureimport/StructureImporter.java | 3 ++- 47 files changed, 155 insertions(+), 76 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java index 7691619ac39..26e3d6c02b1 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java @@ -22,6 +22,8 @@ public interface CompetitorDTO extends Serializable, MappableToDevice { String getName(); Color getColor(); + + String getEmail(); /** * A regular instance will simply return this object. A compacted version may compute the result by looking it up diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java index 96d77c6614a..ef22f9f6a81 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java @@ -19,16 +19,18 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial private String twoLetterIsoCountryCode; private String threeLetterIocCountryCode; private Color color; + private String email; private String sailID; private String idAsString; private BoatClassDTO boatClass; public CompetitorDTOImpl() {} - public CompetitorDTOImpl(String name, Color color, String twoLetterIsoCountryCode, String threeLetterIocCountryCode, + public CompetitorDTOImpl(String name, Color color, String email, String twoLetterIsoCountryCode, String threeLetterIocCountryCode, String countryName, String sailID, String idAsString, BoatClassDTO boatClass) { super(name); this.color = color; + this.email = email; this.twoLetterIsoCountryCode = twoLetterIsoCountryCode; this.threeLetterIocCountryCode = threeLetterIocCountryCode; this.countryName = countryName; @@ -45,6 +47,7 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial result = prime * result + ((idAsString == null) ? 0 : idAsString.hashCode()); result = prime * result + ((sailID == null) ? 0 : sailID.hashCode()); result = prime * result + ((color == null) ? 0 : color.hashCode()); + result = prime * result + ((email == null) ? 0 : email.hashCode()); result = prime * result + ((threeLetterIocCountryCode == null) ? 0 : threeLetterIocCountryCode.hashCode()); return result; } @@ -83,6 +86,11 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial return false; } else if (!color.equals(other.color)) return false; + if (email == null) { + if (other.email != null) + return false; + } else if (!email.equals(other.email)) + return false; return true; } @@ -124,4 +132,12 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial public Color getColor() { return color; } + + public String getEmail() { + return email; + } + + public String email(){ + return email; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java index 0cee29c1a92..33679aee69e 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java @@ -92,6 +92,12 @@ public class PreviousCompetitorDTOImpl implements CompetitorDTO { throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+ " need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization"); } + + @Override + public String getEmail() { + throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+ + " need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization"); + } @Override public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java index e15e16f2a28..e6cb2e8c28c 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java @@ -40,7 +40,7 @@ public class AbstractGPSFixStoreTest { protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a"); protected RaceLog raceLog; protected GPSFixStore store; - protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null); + protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null); protected final Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark"); private final AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java index bff12901d90..f124610bc27 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java @@ -168,7 +168,7 @@ public class CreateAndTrackWithRaceLogTest { assertFalse(raceLog.isEmpty()); // add a mapping and one fix in, one out of mapping - Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null); + Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null, null); DeviceIdentifier dev1 = new SmartphoneImeiIdentifier("dev1"); raceLog.add(factory.createDeviceCompetitorMappingEvent(t(), author, dev1, comp1, 0, t(0), t(10))); addFixes0(dev1); @@ -202,7 +202,7 @@ public class CreateAndTrackWithRaceLogTest { adapter.denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, "race"); // add a mapping and one fix in, one out of mapping - Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null); + Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null, null); DeviceIdentifier dev1 = new SmartphoneImeiIdentifier("dev1"); regattaLog.add(new RegattaLogDeviceCompetitorMappingEventImpl(t(), author, t(), UUID.randomUUID(), comp1, dev1, t(0), t(10))); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java index 0647e02d62a..e4c276dd8ce 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java @@ -49,7 +49,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest { @Test public void areFixesStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException, InterruptedException { - Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null); + Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null); Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2"); DeviceIdentifier device2 = new SmartphoneImeiIdentifier("imei2"); DeviceIdentifier device3 = new SmartphoneImeiIdentifier("imei3"); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java index fea24c2afea..8d65ec10e49 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java @@ -15,6 +15,10 @@ public interface Competitor extends Named, WithID, IsManagedByCache listeners; + private String email; public CompetitorImpl(Serializable id, String name, Color color, DynamicTeam team, DynamicBoat boat) { this.id = id; @@ -73,7 +74,7 @@ public class CompetitorImpl implements DynamicCompetitor { @Override public Competitor resolve(SharedDomainFactory domainFactory) { - Competitor result = domainFactory.getOrCreateCompetitor(getId(), getName(), getColor(), getTeam(), getBoat()); + Competitor result = domainFactory.getOrCreateCompetitor(getId(), getName(), getColor(), getEmail(), getTeam(), getBoat()); return result; } @@ -114,4 +115,17 @@ public class CompetitorImpl implements DynamicCompetitor { return new HashSet(listeners); } } + + public String getEmail() { + return email; + } + + public void setEmail(String eMail) { + //TODO: add E-Mail verification from SecurityServiceImpl + this.email = eMail; + } + + public boolean hasEmail(){ + return !email.isEmpty(); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java index 768c6f15255..409d0475290 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java @@ -9,4 +9,5 @@ public interface DynamicCompetitor extends Competitor, Renamable { DynamicTeam getTeam(); void setColor(Color displayColor); + void setEmail(String email); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java index 0d6a8cd5f42..3e6a0ecf5d0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java @@ -332,11 +332,11 @@ public class SharedDomainFactoryImpl implements SharedDomainFactory { } @Override - public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { + public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, String email, DynamicTeam team, DynamicBoat boat) { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "getting or creating competitor "+name+" with ID "+competitorId+" in domain factory "+this); } - return getCompetitorStore().getOrCreateCompetitor(competitorId, name, displayColor, team, boat); + return getCompetitorStore().getOrCreateCompetitor(competitorId, name, displayColor, email, team, boat); } @Override diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java index 16051ae3835..5449d5a2cdf 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java @@ -93,7 +93,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab } @Override - public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { + public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, String email, DynamicTeam team, DynamicBoat boat) { Competitor result = getExistingCompetitorById(competitorId); // avoid synchronization for successful read access if (result == null) { LockUtil.lockForWrite(lock); @@ -106,7 +106,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab LockUtil.unlockAfterWrite(lock); } } else if (isCompetitorToUpdateDuringGetOrCreate(result)) { - updateCompetitor(result.getId().toString(), name, displayColor, boat.getSailID(), team.getNationality(), + updateCompetitor(result.getId().toString(), name, displayColor, email, boat.getSailID(), team.getNationality(), team.getImage()); competitorNoLongerToUpdateDuringGetOrCreate(result); } @@ -191,7 +191,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab } @Override - public Competitor updateCompetitor(String idAsString, String newName, Color newDisplayColor, String newSailId, + public Competitor updateCompetitor(String idAsString, String newName, Color newDisplayColor, String newEmail, String newSailId, Nationality newNationality, URI newTeamImageUri) { DynamicCompetitor competitor = (DynamicCompetitor) getExistingCompetitorByIdAsString(idAsString); if (competitor != null) { @@ -199,6 +199,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab try { competitor.setName(newName); competitor.setColor(newDisplayColor); + competitor.setEmail(newEmail); competitor.getBoat().setSailId(newSailId); competitor.getTeam().setNationality(newNationality); competitor.getTeam().setImage(newTeamImageUri); @@ -229,7 +230,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab if (competitorDTO == null) { final Nationality nationality = c.getTeam().getNationality(); CountryCode countryCode = nationality == null ? null : nationality.getCountryCode(); - competitorDTO = new CompetitorDTOImpl(c.getName(), c.getColor(), countryCode == null ? "" + competitorDTO = new CompetitorDTOImpl(c.getName(), c.getColor(), c.getEmail(), countryCode == null ? "" : countryCode.getTwoLetterISOCode(), countryCode == null ? "" : countryCode.getThreeLetterIOCCode(), countryCode == null ? "" : countryCode.getName(), c .getBoat().getSailID(), c.getId().toString(), new BoatClassDTO(c.getBoat().getBoatClass() diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index 7f4d7c96c96..673737f2f5b 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -124,7 +124,7 @@ public class DomainFactoryImpl implements DomainFactory { teamMembers.add(person); } DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); - result = competitorStore.getOrCreateCompetitor(competitor.getID(), competitor.getName(), null /*displayColor*/, team, boat); + result = competitorStore.getOrCreateCompetitor(competitor.getID(), competitor.getName(), null /*displayColor*/, null /*email*/, team, boat); } return result; } @@ -140,7 +140,7 @@ public class DomainFactoryImpl implements DomainFactory { } DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); result = baseDomainFactory.getCompetitorStore().getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), raceId, boatClass), - competitor.getName(), null /*displayColor*/, team, boat); + competitor.getName(), null /*displayColor*/, null /*email*/, team, boat); return result; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java index dd915231da5..724aac107ce 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java @@ -232,7 +232,7 @@ public class LeaderboardDTODiffingTest { @Test public void testCompetitorListChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", new BoatClassDTO("505", 5.05)); newVersion.competitors.add(13, somebodyNew); // insert a competitor; this should mess up all others' indexes; check if this works CompetitorDTO wolfgang = getPreviousCompetitorByName("HUNGER +JESS"); @@ -252,7 +252,7 @@ public class LeaderboardDTODiffingTest { Util.addAll(newVersion.getSuppressedCompetitors(), newSuppressedCompetitors); newVersion.setSuppressedCompetitors(newSuppressedCompetitors); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", new BoatClassDTO("505", 5.05)); newVersion.setSuppressed(newVersion.competitors.get(13), true); // suppress an existing competitor; compaction should reduce this to a single number only newVersion.setSuppressed(somebodyNew, true); // check that mixed mode with existing and new competitors works as well @@ -274,7 +274,7 @@ public class LeaderboardDTODiffingTest { @Test public void testDisplayNameChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", new BoatClassDTO("505", 5.05)); newVersion.competitors.add(somebodyNew); newVersion.competitorDisplayNames = new HashMap(newVersion.competitorDisplayNames); @@ -303,7 +303,7 @@ public class LeaderboardDTODiffingTest { List newOrdering = new ArrayList(newVersion.getCompetitorsFromBestToWorst(r9)); newVersion.setCompetitorsFromBestToWorst(r9, newOrdering); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", new BoatClassDTO("505", 5.05)); newVersion.competitors.add(somebodyNew); newOrdering.add(somebodyNew); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java index 9870f58ac90..35c0c694983 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java @@ -24,7 +24,7 @@ public abstract class AbstractLeaderboardTest { } public static Competitor createCompetitor(String competitorName, CompetitorFactory competitorFactory) { - return competitorFactory.getOrCreateCompetitor(competitorName, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( + return competitorFactory.getOrCreateCompetitor(competitorName, competitorName, Color.RED, "someone@nobody.de", new TeamImpl("STG", Collections.singleton( new PersonImpl(competitorName, new NationalityImpl("GER"), /* dateOfBirth */ null, "This is famous "+competitorName)), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index 289ecceb33e..650902ae32a 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -259,7 +259,7 @@ public class DomainFactoryImpl implements DomainFactory { } DynamicTeam team = getOrCreateTeam(name, nationality, competitorId); DynamicBoat boat = new BoatImpl(shortName, boatClass, shortName); - result = competitorStore.getOrCreateCompetitor(competitorId, name, null /*displayColor*/, team, boat); + result = competitorStore.getOrCreateCompetitor(competitorId, name, null /*displayColor*/, null /*email*/, team, boat); } return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java index aafc1d26917..3dffba3a7f5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java @@ -10,7 +10,7 @@ public interface DynamicCompetitorStore extends CompetitorStore { DynamicCompetitor getExistingCompetitorById(Serializable competitorId); @Override - DynamicCompetitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat); + DynamicCompetitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, String email, DynamicTeam team, DynamicBoat boat); @Override Iterable getCompetitors(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java index 41bfb97db8b..a598b10a738 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java @@ -72,6 +72,16 @@ public class DummyMarkPassingWithTimePointOnly implements MarkPassing { @Override public void removeCompetitorChangeListener(CompetitorChangeListener listener) { } + + @Override + public String getEmail() { + return null; + } + + @Override + public boolean hasEmail() { + return false; + } }; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index f6742beb834..32840766a85 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -38,6 +38,7 @@ public class CompetitorEditDialog extends DataEntryDialog { private final TextBox displayColorTextBox; private final ListBox threeLetterIocCountryCode; private final TextBox sailId; + private final TextBox email; private final StringMessages stringMessages; public CompetitorEditDialog(final StringMessages stringMessages, CompetitorDTO competitorToEdit, @@ -56,6 +57,9 @@ public class CompetitorEditDialog extends DataEntryDialog { if (displayColor instanceof InvalidColor) { result = displayColor.getAsHtml(); } + } else if (valueToValidate.getEmail() == null) { + //TODO: Proper Email-Validation + result = stringMessages.pleaseEnterAnEmail(); } else if (valueToValidate.getBoatClass().getName() == null || valueToValidate.getBoatClass().getName().isEmpty()) { result = stringMessages.pleaseEnterABoatClass(); } @@ -89,6 +93,7 @@ public class CompetitorEditDialog extends DataEntryDialog { } this.name = createTextBox(competitorToEdit.getName()); + this.email = createTextBox(competitorToEdit.getEmail()); this.displayColorTextBox = createTextBox(competitorToEdit.getColor() == null ? "" : competitorToEdit.getColor().getAsHtml()); this.threeLetterIocCountryCode = createListBox(/* isMultipleSelect */ false); CountryCodeFactory ccf = CountryCodeFactory.INSTANCE; @@ -170,7 +175,7 @@ public class CompetitorEditDialog extends DataEntryDialog { } } BoatClassDTO boatClass = new BoatClassDTO(boatClassName.getValue(boatClassName.getSelectedIndex()), 0); - CompetitorDTO result = new CompetitorDTOImpl(name.getText(), color, + CompetitorDTO result = new CompetitorDTOImpl(name.getText(), color, email.getText(), /* twoLetterIsoCountryCode */ null, threeLetterIocCountryCode.getValue(threeLetterIocCountryCode.getSelectedIndex()), /* countryName */ null, sailId.getText(), competitorToEdit.getIdAsString(), @@ -189,8 +194,10 @@ public class CompetitorEditDialog extends DataEntryDialog { result.setWidget(2, 1, threeLetterIocCountryCode); result.setWidget(3, 0, new Label(stringMessages.color())); result.setWidget(3, 1, displayColorTextBox); - result.setWidget(4, 0, new Label(stringMessages.boatClass())); - result.setWidget(4, 1, boatClassName); + result.setWidget(4, 0, new Label(stringMessages.email())); + result.setWidget(4, 1, email); + result.setWidget(5, 0, new Label(stringMessages.boatClass())); + result.setWidget(5, 1, boatClassName); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 29396b72a89..84f0b36e837 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1137,4 +1137,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String error(); String setAsActive(); String fileStorage(); + String email(); + String pleaseEnterAnEmail(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index c4923f7954a..ae3c0de339f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1119,4 +1119,6 @@ errorTryingToRegisterRacesForTracking=Error trying to register races {0} for tra errorDeterminingPolarAvailability=Error determining availability of polar / VPP data for race {0}: {1} error=Error setAsActive=Set as active -fileStorage=File Storage \ No newline at end of file +fileStorage=File Storage +email=E-Mail +pleaseEnterAnEmail=Please enter an E-Mail. \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index c03c51ca476..b84a2fa2a8d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1108,4 +1108,6 @@ errorTryingToRegisterRacesForTracking=Fehler beim Versuch, die Wettfahrten {0} z errorDeterminingPolarAvailability=Fehler bei der Verfügbarkeitsprüfung für Polardaten für Wettfahrt {0}: {1} error=Fehler setAsActive=Aktiv setzen -fileStorage=Datei Speicherung \ No newline at end of file +fileStorage=Datei Speicherung +email=E-Mail +pleaseEnterAnEmail=Bitte geben Sie eine E-Mail ein. \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index f2b8636fc77..f18b9b14bee 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1056,4 +1056,6 @@ errorTryingToRegisterRacesForTracking=Error trying to register races {0} for tra errorDeterminingPolarAvailability=Error determining availability of polar / VPP data for race {0}: {1} error=Error setAsActive=Set as active -fileStorage=File Storage \ No newline at end of file +fileStorage=File Storage +email=�маль +pleaseEnterAnEmail=Пожалуй�та, введите �маль \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 6efa709ca1d..98fcb2ce790 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -4536,12 +4536,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S DynamicBoat boat = new BoatImpl(competitor.getName() + " boat", boatClass, competitor.getSailID()); result = getBaseDomainFactory().convertToCompetitorDTO( getBaseDomainFactory().getOrCreateCompetitor(UUID.randomUUID(), competitor.getName(), - competitor.getColor(), team, boat)); + competitor.getColor(), competitor.getEmail(), team, boat)); } else { result = getBaseDomainFactory().convertToCompetitorDTO( getService().apply( new UpdateCompetitor(competitor.getIdAsString(), competitor.getName(), competitor - .getColor(), competitor.getSailID(), nationality, existingCompetitor.getTeam().getImage()))); + .getColor(), competitor.getEmail(), competitor.getSailID(), nationality, existingCompetitor.getTeam().getImage()))); } return result; } diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java index 84b39d90664..d40fac7e98b 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java @@ -174,7 +174,7 @@ public class StoreAndLoadRaceLogEventsTest extends AbstractMongoDBTest { @Test public void testStoreAndLoadDeviceCompetitorMappingEvent() { DeviceIdentifier device = new SmartphoneImeiIdentifier("a"); - Competitor mappedTo = DomainFactory.INSTANCE.getOrCreateCompetitor("abc", "abc", null, null, null); + Competitor mappedTo = DomainFactory.INSTANCE.getOrCreateCompetitor("abc", "abc", null, null, null, null); TimePoint from = new MillisecondsTimePoint(20); TimePoint to = new MillisecondsTimePoint(30); RaceLogDeviceCompetitorMappingEvent expectedEvent = eventFactory.createDeviceCompetitorMappingEvent( @@ -251,7 +251,7 @@ public class StoreAndLoadRaceLogEventsTest extends AbstractMongoDBTest { public void testStoreAndLoadRegisterCompetitorEvent() { RaceLogRegisterCompetitorEvent expectedEvent = eventFactory.createRegisterCompetitorEvent( expectedEventTime, author, expectedEventTime, expectedId, expectedPassId, - DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null)); + DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null)); DBObject dbObject = mongoFactory.storeRaceLogEntry(logIdentifier, expectedEvent); RaceLogRegisterCompetitorEvent actualEvent = loadEvent(dbObject); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java index f1adb9b63e6..078dcfd56fd 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java @@ -58,7 +58,7 @@ public class StoreGPSFixTrackTest extends AbstractMongoDBTest { public void testStoreAndLoadFixes() throws TransformationException, NoCorrespondingServiceRegisteredException { TypeBasedServiceFinderFactory factory = new MockSmartphoneImeiServiceFinderFactory(); AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0); - Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null); + Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null); Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark"); MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService(), factory); DomainObjectFactory domainObjectFactory = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE, factory); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java index d9fa5489325..5d849ea0eb6 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java @@ -350,7 +350,7 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { final DomainFactory domainFactory = new DomainFactoryImpl(); // create the competitor through the competitor store/factory here so that the DomainObjectFactory finds it and // resolves the score corrections appropriate - Competitor competitor = domainFactory.getOrCreateCompetitor(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( + Competitor competitor = domainFactory.getOrCreateCompetitor(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, "someone@nowhere.de", new TeamImpl("STG", Collections.singleton( new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java index ba6a79af120..9ba8b08d2a0 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java @@ -273,7 +273,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD @Test public void testStoreAndRetrieveSimpleLeaderboardWithRaceLogFinishPositioningConfirmedEvent() { - Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, null, null); + Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, "someone@nowhere.de", null, null); CompetitorResults storedPositioningList = new CompetitorResultsImpl(); storedPositioningList.add(new com.sap.sse.common.Util.Triple(storedCompetitor.getId(), storedCompetitor.getName(), MaxPointsReason.NONE)); @@ -342,7 +342,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD @Test public void testStoreAndRetrieveSimpleLeaderboardWithRaceLogFinishPositioningListChangeEvent() { - Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, null, null); + Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, "someone@nowhere.de", null, null); CompetitorResults storedPositioningList = new CompetitorResultsImpl(); storedPositioningList.add(new com.sap.sse.common.Util.Triple(storedCompetitor.getId(), storedCompetitor.getName(), MaxPointsReason.NONE)); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java index ede401bb4a2..31bc1d59e72 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java @@ -187,7 +187,7 @@ public class TestStoringAndRetrievingRaceLogInRegatta extends AbstractTestStorin @Test public void testStoreAndRetrieveRegattaWithRaceLogFinishPositioningListChangeEvent() { - Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, null, null); + Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, "someone@nowhere.de", null, null); CompetitorResults storedPositioningList = new CompetitorResultsImpl(); storedPositioningList.add(new com.sap.sse.common.Util.Triple(storedCompetitor.getId(), storedCompetitor.getName(), MaxPointsReason.NONE)); @@ -217,7 +217,7 @@ public class TestStoringAndRetrievingRaceLogInRegatta extends AbstractTestStorin @Test public void testStoreAndRetrieveRegattaWithRaceLogFinishPositioningConfirmedEvent() { - Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, null, null); + Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED, "someone@nowhere.de", null, null); CompetitorResults storedPositioningList = new CompetitorResultsImpl(); storedPositioningList.add(new com.sap.sse.common.Util.Triple(storedCompetitor.getId(), storedCompetitor.getName(), MaxPointsReason.NONE)); diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/CompetitorJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/CompetitorJsonDeserializer.java index dcd7490de68..84c7591ac7e 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/CompetitorJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/CompetitorJsonDeserializer.java @@ -51,6 +51,8 @@ public class CompetitorJsonDeserializer implements JsonDeserializer } String name = (String) object.get(CompetitorJsonSerializer.FIELD_NAME); String displayColorAsString = (String) object.get(CompetitorJsonSerializer.FIELD_DISPLAY_COLOR); + String email = (String) object.get(CompetitorJsonSerializer.FIELD_EMAIL); + final Color displayColor; if (displayColorAsString == null || displayColorAsString.isEmpty()) { displayColor = null; @@ -67,7 +69,8 @@ public class CompetitorJsonDeserializer implements JsonDeserializer boat = boatJsonDeserializer.deserialize(Helpers.getNestedObjectSafe(object, CompetitorJsonSerializer.FIELD_BOAT)); } - Competitor competitor = competitorStore.getOrCreateCompetitor(competitorId, name, displayColor, team, boat); + + Competitor competitor = competitorStore.getOrCreateCompetitor(competitorId, name, displayColor, email, team, boat); return competitor; } catch (Exception e) { throw new JsonDeserializationException(e); diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java index 8a42dcb3b06..8f255cdddaa 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java @@ -30,6 +30,7 @@ public class CompetitorJsonSerializer implements JsonSerializer { public static final String FIELD_TEAM = "team"; public static final String FIELD_BOAT = "boat"; public static final String FIELD_DISPLAY_COLOR = "displayColor"; + public static final String FIELD_EMAIL = "email"; private final JsonSerializer teamJsonSerializer; private final JsonSerializer boatJsonSerializer; @@ -66,6 +67,7 @@ public class CompetitorJsonSerializer implements JsonSerializer { } result.put(FIELD_NAME, competitor.getName()); result.put(FIELD_DISPLAY_COLOR, competitor.getColor() == null ? null : competitor.getColor().getAsHtml()); + result.put(FIELD_EMAIL, competitor.getEmail()); result.put(FIELD_SAILID, competitor.getBoat() == null ? "" : competitor.getBoat().getSailID()); final Nationality nationality = competitor.getTeam() == null ? null : competitor.getTeam().getNationality(); result.put(FIELD_NATIONALITY, nationality == null ? "" : nationality.getThreeLetterIOCAcronym()); diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java index 0e35f55d5bc..1d3f145cbdf 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java @@ -237,7 +237,7 @@ public class RaceLogEventSerializerTest { public void testRegisterCompetitorEventSerializer() { // we use the real event type here because we do not want to re-implement the dispatching. RaceLogEvent event = factory.createRegisterCompetitorEvent(null, author, 0, - DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null)); + DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null)); serializer.serialize(event); verify(registerCompetitorEventSerializer).serialize(event); } diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTest.java index 43855aa01f0..4c04bd95597 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTest.java @@ -34,7 +34,7 @@ public class CompetitorsResourceTest extends AbstractJaxRsApiTest { super.setUp(); DynamicTeam team = new TeamImpl(null, Collections.singleton(new PersonImpl(null, new NationalityImpl(nationality), null, null)), null); DynamicBoat boat = new BoatImpl(null, new BoatClassImpl(boatClassName, false), sailID); - racingEventService.getBaseDomainFactory().getOrCreateCompetitor(id, name, null, team, boat); + racingEventService.getBaseDomainFactory().getOrCreateCompetitor(id, name, null, null, team, boat); } @Test diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java index e5302d47c0a..29a39aed17c 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceCheckinAndOutTest.java @@ -44,8 +44,8 @@ public class LeaderboardsResourceCheckinAndOutTest extends AbstractJaxRsApiTest public void setUp() { super.setUp(); Competitor c = createCompetitors(1).get(0); - competitor = racingEventService.getBaseDomainFactory().getOrCreateCompetitor(c.getId(), c.getName(), c.getColor(), - (DynamicTeam) c.getTeam(), (DynamicBoat) c.getBoat()); + competitor = racingEventService.getBaseDomainFactory().getOrCreateCompetitor(c.getId(), c.getName(), + c.getColor(), c.getEmail(), (DynamicTeam) c.getTeam(), (DynamicBoat) c.getBoat()); Regatta regatta = new RegattaImpl("regatta", new BoatClassImpl("49er", false), MillisecondsTimePoint.now(), MillisecondsTimePoint.now(), Collections.singleton(new SeriesImpl("series", false, Collections @@ -62,7 +62,7 @@ public class LeaderboardsResourceCheckinAndOutTest extends AbstractJaxRsApiTest LeaderboardsResource resource = spyResource(new LeaderboardsResource()); UUID deviceUuid = UUID.randomUUID(); - //checkin + // checkin long fromMillis = 500; JSONObject json = new JSONObject(); json.put(DeviceMappingConstants.JSON_COMPETITOR_ID_AS_STRING, competitor.getId().toString()); @@ -82,22 +82,24 @@ public class LeaderboardsResourceCheckinAndOutTest extends AbstractJaxRsApiTest DeviceMapping mappingForC = mappingsForC.get(0); assertThat("that mapping is for the correct device", ((SmartphoneUUIDIdentifier) mappingForC.getDevice()).getUUID(), equalTo(deviceUuid)); - assertThat("that mapping starts at the correct timepoint", mappingForC.getTimeRange().from().asMillis(), equalTo(fromMillis)); + assertThat("that mapping starts at the correct timepoint", mappingForC.getTimeRange().from().asMillis(), + equalTo(fromMillis)); assertThat("that mapping is open-ended", mappingForC.getTimeRange().hasOpenEnd(), equalTo(true)); - - //checkout + + // checkout long toMillis = 1000; json = new JSONObject(); json.put(DeviceMappingConstants.JSON_TO_MILLIS, toMillis); json.put(DeviceMappingConstants.JSON_COMPETITOR_ID_AS_STRING, competitor.getId().toString()); json.put(DeviceMappingConstants.JSON_DEVICE_UUID, deviceUuid.toString()); - + response = resource.postCheckout(json.toString(), leaderboard.getName()); assertThat("checkout returns OK", response.getStatus(), equalTo(Response.Status.OK.getStatusCode())); - + mappings = new DeviceCompetitorMappingFinder<>(log).analyze(); mappingForC = mappings.get(competitor).get(0); - assertThat("mapping now ends at checkout timepoint", mappingForC.getTimeRange().to().asMillis(), equalTo(toMillis)); + assertThat("mapping now ends at checkout timepoint", mappingForC.getTimeRange().to().asMillis(), + equalTo(toMillis)); } } diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java index 61576a65b08..0c6b0ccb51f 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java @@ -54,7 +54,7 @@ public class TeamImageTest extends AbstractJaxRsApiTest { DynamicTeam team = new TeamImpl(null, Collections.singleton(new PersonImpl(null, new NationalityImpl( nationality), null, null)), null); DynamicBoat boat = new BoatImpl(null, new BoatClassImpl(boatClassName, false), sailID); - racingEventService.getBaseDomainFactory().getOrCreateCompetitor(id, name, null, team, boat); + racingEventService.getBaseDomainFactory().getOrCreateCompetitor(id, name, null, null, team, boat); } @Test diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java index e182dd3181e..07b20fe2c2c 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java @@ -141,7 +141,7 @@ public class CompetitorsResource extends AbstractSailingServerResource { .entity("Could not store competitor image").type(MediaType.TEXT_PLAIN).build()); } - getService().getCompetitorStore().updateCompetitor(competitorId, competitor.getName(), competitor.getColor(), + getService().getCompetitorStore().updateCompetitor(competitorId, competitor.getName(), competitor.getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), imageUri); logger.log(Level.INFO, "Set team image for competitor " + competitor.getName()); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java index a26e7f3dbf1..0756050f030 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/CompetitorReplicationTest.java @@ -76,7 +76,7 @@ public class CompetitorReplicationTest extends AbstractServerReplicationTest { Iterable emptyWaypointList = Collections.emptyList(); final String competitorName = "Der mit dem Kiel zieht"; Competitor competitor = master.getBaseDomainFactory().getOrCreateCompetitor( - 123, competitorName, Color.RED, + 123, competitorName, Color.RED, "someone@nowhere.de", new TeamImpl("STG", Collections.singleton(new PersonImpl(competitorName, new NationalityImpl("GER"), /* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), @@ -106,7 +106,7 @@ public class CompetitorReplicationTest extends AbstractServerReplicationTest { // now update competitor on master using replicating operation final String newCompetitorName = "Der Vogel, der mit dem Kiel zieht"; - master.apply(new UpdateCompetitor(competitor.getId().toString(), newCompetitorName, competitor.getColor(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), + master.apply(new UpdateCompetitor(competitor.getId().toString(), newCompetitorName, competitor.getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), competitor.getTeam().getImage())); Thread.sleep(1000); assertEquals(newCompetitorName, replicatedCompetitor.getName()); // expect in-place update of existing competitor in replica @@ -114,7 +114,7 @@ public class CompetitorReplicationTest extends AbstractServerReplicationTest { // now allow for resetting to default through some event, such as receiving a GPS position master.apply(new AllowCompetitorResetToDefaults(Collections.singleton(competitor.getId().toString()))); // modify the competitor on the master "from below" without an UpdateCompetitor operation, only locally: - master.getBaseDomainFactory().getCompetitorStore().updateCompetitor(competitor.getId().toString(), competitorName, Color.RED, + master.getBaseDomainFactory().getCompetitorStore().updateCompetitor(competitor.getId().toString(), competitorName, Color.RED, competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), competitor.getTeam().getImage()); final RegattaAndRaceIdentifier raceIdentifier = masterRegatta.getRaceIdentifier(raceDefinition); DynamicTrackedRace trackedRace = (DynamicTrackedRace) master.apply(new CreateTrackedRace(raceIdentifier, diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java index 047ecd2589c..d045f487598 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceContentsReplicationTest.java @@ -71,7 +71,7 @@ public class TrackedRaceContentsReplicationTest extends AbstractServerReplicatio // FIXME use master DomainFactory; see bug 592 final DomainFactory masterDomainFactory = DomainFactory.INSTANCE; BoatClass boatClass = masterDomainFactory.getOrCreateBoatClass(boatClassName, /* typicallyStartsUpwind */true); - competitor = masterDomainFactory.getCompetitorStore().getOrCreateCompetitor("GER 61", "Tina Lutz", Color.RED, new TeamImpl("Tina Lutz + Susann Beucke", + competitor = masterDomainFactory.getCompetitorStore().getOrCreateCompetitor("GER 61", "Tina Lutz", Color.RED, "someone@nowhere.de", new TeamImpl("Tina Lutz + Susann Beucke", (List) Arrays.asList(new PersonImpl[] { new PersonImpl("Tina Lutz", DomainFactory.INSTANCE.getOrCreateNationality("GER"), null, null), new PersonImpl("Tina Lutz", DomainFactory.INSTANCE.getOrCreateNationality("GER"), null, null) }), new PersonImpl("Rigo de Mas", DomainFactory.INSTANCE.getOrCreateNationality("NED"), null, null)), diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/CompetitorStoreTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/CompetitorStoreTest.java index e26d57c9e15..5b4a1c95bf1 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/CompetitorStoreTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/CompetitorStoreTest.java @@ -22,20 +22,20 @@ public class CompetitorStoreTest { public void testAddingToTransientStore() { CompetitorStore transientStore = new TransientCompetitorStoreImpl(); DynamicCompetitor template = AbstractLeaderboardTest.createCompetitor("Test Competitor"); - Competitor competitor = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getTeam(), template.getBoat()); + Competitor competitor = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), template.getTeam(), template.getBoat()); assertTrue(competitor != template); - Competitor competitor2 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getTeam(), template.getBoat()); + Competitor competitor2 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), template.getTeam(), template.getBoat()); assertSame(competitor, competitor2); assertEquals(DomainFactory.INSTANCE.getOrCreateNationality("GER"), competitor.getTeam().getNationality()); DynamicTeam differentTeam = AbstractLeaderboardTest.createCompetitor("Test Competitor").getTeam(); differentTeam.setNationality(DomainFactory.INSTANCE.getOrCreateNationality("GHA")); // Ghana - Competitor competitor3 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), differentTeam, template.getBoat()); + Competitor competitor3 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), differentTeam, template.getBoat()); assertSame(competitor, competitor3); // use existing competitor despite the different team assertSame(competitor.getTeam(), competitor3.getTeam()); // now mark the competitor as to update from defaults transientStore.allowCompetitorResetToDefaults(competitor); - Competitor competitor4 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), differentTeam, template.getBoat()); + Competitor competitor4 = transientStore.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), differentTeam, template.getBoat()); assertSame(competitor, competitor4); // expecting an in-place update assertEquals(differentTeam.getNationality(), competitor4.getTeam().getNationality()); } @@ -44,7 +44,7 @@ public class CompetitorStoreTest { public void testPersistentCompetitorStore() { CompetitorStore persistentStore1 = new PersistentCompetitorStore(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(), /* clearStore */ true, null); DynamicCompetitor template = AbstractLeaderboardTest.createCompetitor("Test Competitor"); - Competitor competitor = persistentStore1.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getTeam(), template.getBoat()); + Competitor competitor = persistentStore1.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), template.getTeam(), template.getBoat()); CompetitorStore persistentStore2 = new PersistentCompetitorStore(PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(), /* clearStore */ false, null); Competitor competitor2 = persistentStore2.getExistingCompetitorById(template.getId()); @@ -53,14 +53,14 @@ public class CompetitorStoreTest { assertEquals(template.getTeam().getNationality(), competitor2.getTeam().getNationality()); DynamicTeam differentTeam = AbstractLeaderboardTest.createCompetitor("Test Competitor").getTeam(); differentTeam.setNationality(DomainFactory.INSTANCE.getOrCreateNationality("GHA")); // Ghana - Competitor competitor3 = persistentStore2.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), differentTeam, template.getBoat()); + Competitor competitor3 = persistentStore2.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), differentTeam, template.getBoat()); assertSame(competitor2, competitor3); // use existing competitor despite the different team assertNotSame(differentTeam, competitor2.getTeam()); // team expected to remain unchanged assertEquals(competitor.getTeam().getNationality(), competitor3.getTeam().getNationality()); // no updatability requested; nationality expected to remain unchanged // now mark the competitor as to update from defaults persistentStore2.allowCompetitorResetToDefaults(competitor2); - Competitor competitor4 = persistentStore2.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), differentTeam, template.getBoat()); + Competitor competitor4 = persistentStore2.getOrCreateCompetitor(template.getId(), template.getName(), template.getColor(), template.getEmail(), differentTeam, template.getBoat()); assertSame(competitor2, competitor4); // expecting an in-place update assertEquals(differentTeam.getNationality(), competitor4.getTeam().getNationality()); } diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardStorageTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardStorageTest.java index df63bd3332f..fa34ead7e9e 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardStorageTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardStorageTest.java @@ -61,7 +61,7 @@ public class LeaderboardStorageTest extends TestCase { DynamicBoat boat = new BoatImpl("woot", service.getBaseDomainFactory().getOrCreateBoatClass("H16"), "70155"); String competitorId = "testC"; Competitor competitor = service.getBaseDomainFactory().getOrCreateCompetitor(competitorId, "Test C", null, - team, boat); + null, team, boat); double carriedPoints = 2.0; leaderboard.setCarriedPoints(competitor, carriedPoints); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index 24bdb4998e0..749426ba010 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -749,7 +749,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - Competitor competitor = sourceDomainFactory.getOrCreateCompetitor(competitorUUID, "Froderik", Color.RED, team, + Competitor competitor = sourceDomainFactory.getOrCreateCompetitor(competitorUUID, "Froderik", Color.RED, "noone@nowhere.de", team, boat); competitors.add(competitor); UUID competitor2UUID = UUID.randomUUID(); @@ -759,7 +759,7 @@ public class MasterDataImportTest { DynamicTeam team2 = new TeamImpl("Pros2", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("FastBoat", boatClass, "GER70133"); Competitor competitor2 = sourceDomainFactory.getCompetitorStore().getOrCreateCompetitor(competitor2UUID, - "Froderik", Color.RED, team2, boat2); + "Froderik", Color.RED, "noone@nowhere.de", team2, boat2); competitors.add(competitor2); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -1206,7 +1206,7 @@ public class MasterDataImportTest { DynamicBoat boatToOverride = new BoatImpl("Wingy", boatClassToOverride, "GER70133"); String competitorOldName = "oldName"; Competitor competitorToOverride = domainFactory.getOrCreateCompetitor(competitorUUID, competitorOldName, - Color.BLUE, teamToOverride, boatToOverride); + Color.BLUE, "noone@nowhere.de", teamToOverride, boatToOverride); competitorsToOverride.add(competitorToOverride); Leaderboard leaderboardToOverride = destService.addRegattaLeaderboard( diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java index c400a3a1df3..d9b183abd42 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/WindByRaceLogTest.java @@ -105,7 +105,7 @@ public class WindByRaceLogTest { } private Competitor createCompetitor(final DomainFactory masterDomainFactory) { - return masterDomainFactory.getOrCreateCompetitor("GER 61", "Sailor", Color.RED, new TeamImpl("Sailor", + return masterDomainFactory.getOrCreateCompetitor("GER 61", "Sailor", Color.RED, "noone@nowhere.de", new TeamImpl("Sailor", (List) Arrays.asList(new PersonImpl[] { new PersonImpl("Sailor 1", DomainFactory.INSTANCE.getOrCreateNationality("GER"), null, null)}), new PersonImpl("Sailor 2", DomainFactory.INSTANCE.getOrCreateNationality("NED"), null, null)), new BoatImpl("GER 61", DomainFactory.INSTANCE.getOrCreateBoatClass("470", /* typicallyStartsUpwind */ true), "GER 61")); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/PersistentCompetitorStore.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/PersistentCompetitorStore.java index 380907e07af..496bddb79ba 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/PersistentCompetitorStore.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/PersistentCompetitorStore.java @@ -96,9 +96,9 @@ public class PersistentCompetitorStore extends TransientCompetitorStoreImpl impl } @Override - public Competitor updateCompetitor(String idAsString, String newName, Color newRgbDisplayColor, String newSailId, + public Competitor updateCompetitor(String idAsString, String newName, Color newRgbDisplayColor, String newEmail, String newSailId, Nationality newNationality, URI newTeamImageUri) { - Competitor result = super.updateCompetitor(idAsString, newName, newRgbDisplayColor, newSailId, newNationality, + Competitor result = super.updateCompetitor(idAsString, newName, newRgbDisplayColor, newEmail, newSailId, newNationality, newTeamImageUri); storeTo.storeCompetitor(result); return result; diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 20d934eeeea..c5d68e535b8 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -462,7 +462,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes @Override public void competitorUpdated(Competitor competitor) { replicate(new UpdateCompetitor(competitor.getId().toString(), competitor.getName(), competitor - .getColor(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), + .getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), competitor.getTeam().getImage())); } }); @@ -2354,7 +2354,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes for (Competitor competitor : ((CompetitorStore) ois.readObject()).getCompetitors()) { DynamicCompetitor dynamicCompetitor = (DynamicCompetitor) competitor; competitorStore.getOrCreateCompetitor(dynamicCompetitor.getId(), dynamicCompetitor.getName(), - dynamicCompetitor.getColor(), dynamicCompetitor.getTeam(), dynamicCompetitor.getBoat()); + dynamicCompetitor.getColor(), dynamicCompetitor.getEmail(), dynamicCompetitor.getTeam(), dynamicCompetitor.getBoat()); } logoutput.append("Received " + competitorStore.size() + " NEW competitors\n"); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java index 3daf9b6f90a..c2e0d7d60ca 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateCompetitor.java @@ -14,6 +14,7 @@ public class UpdateCompetitor extends AbstractRacingEventServiceOperationnull, the competitor obtains the "NONE" nationality, usually represented by a white * flag */ - public UpdateCompetitor(String idAsString, String newName, Color newDisplayColor, String newSailId, + public UpdateCompetitor(String idAsString, String newName, Color newDisplayColor, String newEmail, String newSailId, Nationality newNationality, URI newTeamImageUri) { super(); this.idAsString = idAsString; @@ -33,6 +34,7 @@ public class UpdateCompetitor extends AbstractRacingEventServiceOperation Date: Thu, 5 Feb 2015 11:30:22 +0100 Subject: [PATCH 020/264] Fix widget array size --- .../sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index 32840766a85..e7915984ae3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -185,7 +185,7 @@ public class CompetitorEditDialog extends DataEntryDialog { @Override protected Widget getAdditionalWidget() { - Grid result = new Grid(5, 2); + Grid result = new Grid(6, 2); result.setWidget(0, 0, new Label(stringMessages.name())); result.setWidget(0, 1, name); result.setWidget(1, 0, new Label(stringMessages.sailNumber())); From d0407c15842790a547f3f6dab2e8fb658aa3a85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bro=C3=9F?= Date: Thu, 5 Feb 2015 12:31:45 +0100 Subject: [PATCH 021/264] Get additional competitors of racelog in RegattaImpl --- .../sailing/domain/test/mock/MockedTrackedRace.java | 2 +- .../src/com/sap/sailing/domain/base/Regatta.java | 2 +- .../sap/sailing/domain/base/impl/RegattaImpl.java | 12 +++++++++++- .../leaderboard/impl/RegattaLeaderboardImpl.java | 6 ++++++ .../sap/sailing/gwt/ui/test/MockedTrackedRace.java | 2 +- .../serialization/impl/RegattaJsonSerializer.java | 2 +- .../impl/RegattaAndRaceDataJsonGetServlet.java | 2 +- 7 files changed, 22 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index 817943466eb..e40992d97ad 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -290,7 +290,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Iterable getCompetitors() { + public Iterable getAllCompetitors() { return null; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java index 0667d9ade2a..de63c2f8f3e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java @@ -103,7 +103,7 @@ public interface Regatta extends Named, WithID, IsRegattaLike { BoatClass getBoatClass(); - Iterable getCompetitors(); + Iterable getAllCompetitors(); /** * Will remove the series from this regatta. Will also call {@link RaceColumn#removeRaceIdentifier(Fleet)} to make diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index 8dd579aaac4..75c0b58a29e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -12,6 +12,8 @@ import java.util.logging.Logger; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; +import com.sap.sailing.domain.abstractlog.shared.events.RegisterCompetitorEvent; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; @@ -297,13 +299,21 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene } @Override - public Iterable getCompetitors() { + public Iterable getAllCompetitors() { Set result = new HashSet(); for (RaceDefinition race : getAllRaces()) { for (Competitor c : race.getCompetitors()) { result.add(c); } } + + RegattaLog regattaLog = regattaLikeHelper.getRegattaLog(); + for (RegattaLogEvent event : regattaLog.getUnrevokedEvents()){ + if (event instanceof RegisterCompetitorEvent){ + RegisterCompetitorEvent competitorRegistrationEvent = (RegisterCompetitorEvent) event; + result.add(competitorRegistrationEvent.getCompetitor()); + } + } return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java index 0301eb7e8a9..5d4d4fd6f8c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.leaderboard.impl; import java.util.ArrayList; import java.util.List; +import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; @@ -100,4 +101,9 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R public IsRegattaLike getRegattaLike() { return regatta; } + + @Override + public Iterable getAllCompetitors() { + return regatta.getAllCompetitors(); + } } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index bf1191a0747..edd60158632 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -335,7 +335,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Iterable getCompetitors() { + public Iterable getAllCompetitors() { // TODO Auto-generated method stub return null; } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java index 9cb04846da0..b74ad82272b 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java @@ -49,7 +49,7 @@ public class RegattaJsonSerializer implements JsonSerializer { if(competitorSerializer != null) { JSONArray competitorsJson = new JSONArray(); - for (Competitor competitor: regatta.getCompetitors()) { + for (Competitor competitor: regatta.getAllCompetitors()) { competitorsJson.add(competitorSerializer.serialize(competitor)); } result.put(FIELD_COMPETITORS, competitorsJson); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java index 6b9ff74828d..cb774ad9a47 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java @@ -372,7 +372,7 @@ public class RegattaAndRaceDataJsonGetServlet extends AbstractJsonHttpServlet { jsonEvent.put("boatclass", regatta.getBoatClass().getName()); } JSONArray jsonCompetitors = new JSONArray(); - for (Competitor competitor : regatta.getCompetitors()) { + for (Competitor competitor : regatta.getAllCompetitors()) { JSONObject jsonCompetitor = new JSONObject(); jsonCompetitor.put("name", competitor.getName()); jsonCompetitor.put("sailID", competitor.getBoat()==null?"":competitor.getBoat().getSailID()); From 8297307ab5685180611ced5d58c184d6ebfa3cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bro=C3=9F?= Date: Thu, 5 Feb 2015 13:08:35 +0100 Subject: [PATCH 022/264] Add Invite competitors button, not sending out emails yet --- .../domain/common/dto/CompetitorDTO.java | 2 + .../domain/common/dto/CompetitorDTOImpl.java | 5 ++ .../common/dto/PreviousCompetitorDTOImpl.java | 5 ++ .../gwt/ui/adminconsole/CompetitorPanel.java | 46 ++++++++++++++++--- .../sailing/gwt/ui/client/StringMessages.java | 2 + .../gwt/ui/client/StringMessages.properties | 4 +- .../ui/client/StringMessages_de.properties | 4 +- .../ui/client/StringMessages_ru.properties | 4 +- 8 files changed, 62 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java index 26e3d6c02b1..e6e2d7c78ca 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java @@ -24,6 +24,8 @@ public interface CompetitorDTO extends Serializable, MappableToDevice { Color getColor(); String getEmail(); + + boolean hasEmail(); /** * A regular instance will simply return this object. A compacted version may compute the result by looking it up diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java index ef22f9f6a81..c245de77881 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java @@ -140,4 +140,9 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial public String email(){ return email; } + + @Override + public boolean hasEmail() { + return !email.isEmpty(); + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java index 33679aee69e..77ae9538d86 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java @@ -103,4 +103,9 @@ public class PreviousCompetitorDTOImpl implements CompetitorDTO { public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) { return previousVersion.competitors.get(indexInPreviousCompetitorList); } + + @Override + public boolean hasEmail() { + return false; + } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java index 4b0dc656446..7fbf7776214 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java @@ -85,6 +85,35 @@ public class CompetitorPanel extends SimplePanel { } }); buttonPanel.add(addCompetitorButton); + + final Button inviteCompetitorsButton = new Button(stringMessages.inviteCompetitors()); + inviteCompetitorsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + //TODO: check, whether all have an email attached and send email + Iterable Competitors = competitorTable.getAllCompetitors(); + boolean emailProvidedForAll = isEmailProvidedForAll(Competitors); + + if (emailProvidedForAll){ + Window.alert("Sending successfull"); + } else { + Window.alert(stringMessages.notAllCompetitorsProvideEmail()); + } + } + + private boolean isEmailProvidedForAll(Iterable allCompetitors) { + for (CompetitorDTO competitor : allCompetitors){ + if (!competitor.hasEmail()){ + return false; + } + } + + return true; + } + }); + buttonPanel.add(inviteCompetitorsButton); + + competitorsPanel.add(buttonPanel); @@ -117,16 +146,19 @@ public class CompetitorPanel extends SimplePanel { refreshCompetitorList(); } } - + private void allowUpdate(final Iterable competitors) { List serializableSingletonList = new ArrayList(); Util.addAll(competitors, serializableSingletonList); sailingService.allowCompetitorResetToDefaults(serializableSingletonList, new AsyncCallback() { - @Override public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to allow resetting competitors "+competitors - +" to defaults: "+caught.getMessage()); + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to allow resetting competitors " + competitors + + " to defaults: " + caught.getMessage()); } - @Override public void onSuccess(Void result) { + + @Override + public void onSuccess(Void result) { Window.alert(stringMessages.successfullyAllowedCompetitorReset(competitors.toString())); } }); @@ -143,7 +175,7 @@ public class CompetitorPanel extends SimplePanel { sailingService.addOrUpdateCompetitor(competitor, new AsyncCallback() { @Override public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to update competitor: "+caught.getMessage()); + errorReporter.reportError("Error trying to update competitor: " + caught.getMessage()); } @Override @@ -158,7 +190,7 @@ public class CompetitorPanel extends SimplePanel { } }).show(); } - + public void refreshCompetitorList() { competitorTable.refreshCompetitorList(leaderboardName); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 84f0b36e837..663824b7698 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1139,4 +1139,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String fileStorage(); String email(); String pleaseEnterAnEmail(); + String inviteCompetitors(); + String notAllCompetitorsProvideEmail(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index ae3c0de339f..e6d89a9be83 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1121,4 +1121,6 @@ error=Error setAsActive=Set as active fileStorage=File Storage email=E-Mail -pleaseEnterAnEmail=Please enter an E-Mail. \ No newline at end of file +pleaseEnterAnEmail=Please enter an E-Mail. +inviteCompetitors=Invite competitors +notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index b84a2fa2a8d..136cd23dc00 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1110,4 +1110,6 @@ error=Fehler setAsActive=Aktiv setzen fileStorage=Datei Speicherung email=E-Mail -pleaseEnterAnEmail=Bitte geben Sie eine E-Mail ein. \ No newline at end of file +pleaseEnterAnEmail=Bitte geben Sie eine E-Mail ein. +inviteCompetitors=Teilnehmer einladen +notAllCompetitorsProvideEmail=Nicht alle Teilnehmer haben eine E-Mail angegeben \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index f18b9b14bee..da87c21ee55 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1058,4 +1058,6 @@ error=Error setAsActive=Set as active fileStorage=File Storage email=�маль -pleaseEnterAnEmail=Пожалуй�та, введите �маль \ No newline at end of file +pleaseEnterAnEmail=Пожалуй�та, введите �маль +inviteCompetitors=приглашать Уча�тники +notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail \ No newline at end of file From 696165574f960ed6fd939bee944cbb5c59dfdddf Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 5 Feb 2015 14:42:33 +0100 Subject: [PATCH 023/264] implemented sample styleguide (Typeface) --- .../client/showcase/ComponentShowCase.ui.xml | 1 - .../gwt/theme/client/showcase/MainPage.ui.xml | 6 +- .../client/showcase/ShowCaseResources.java | 31 ++++++++ .../SAPFooterShowcase.java | 2 +- .../SAPFooterShowcase.ui.xml | 12 +-- .../SAPHeaderShowcase.java | 2 +- .../SAPHeaderShowcase.ui.xml | 12 +-- .../gwt/theme/client/showcase/showcase.gss | 78 +++++++++++++++++++ .../AbstractStyleguideComposite.java | 13 ++++ .../styleguide/TypefaceStyleguide.java | 19 +++++ .../styleguide/TypefaceStyleguide.ui.xml | 44 +++++++++++ .../client/styleguide/typeface/typeface.html | 24 ------ 12 files changed, 196 insertions(+), 48 deletions(-) create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowCaseResources.java rename java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/{sapfooter => components}/SAPFooterShowcase.java (92%) rename java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/{sapfooter => components}/SAPFooterShowcase.ui.xml (77%) rename java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/{sapheader => components}/SAPHeaderShowcase.java (93%) rename java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/{sapheader => components}/SAPHeaderShowcase.ui.xml (78%) create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/showcase.gss create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/AbstractStyleguideComposite.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.ui.xml delete mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/styleguide/typeface/typeface.html diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml index cfad5712d50..eeabbc8f25d 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ComponentShowCase.ui.xml @@ -25,6 +25,5 @@ - \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml index f14b9af839d..794aff49f7e 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml @@ -9,10 +9,10 @@ Components - + - + @@ -20,7 +20,7 @@ Styleguide - tbd. + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowCaseResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowCaseResources.java new file mode 100644 index 00000000000..2147603cd48 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/ShowCaseResources.java @@ -0,0 +1,31 @@ +package com.sap.sse.gwt.theme.client.showcase; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface ShowCaseResources extends ClientBundle { + public static final ShowCaseResources INSTANCE = GWT.create(ShowCaseResources.class); + + @Source("showcase.gss") + ShowcaseCss css(); + + public interface ShowcaseCss extends CssResource{ + String showcaseHeader(); + String showcaseContent(); + String styleGuideHeader(); + String styleGuideContent(); + + String styleGuideRow(); + String styleGuideRowDescription(); + String styleGuideRowDescriptionTitle(); + String styleGuideRowContent(); + String styleGuideRowCode(); + String styleGuideRowDescription_codeLink(); + + // TODO: these style should come from the main.css + String styleGuideDetailsTypeface_semibold(); + String styleGuideDetailsTypeface_bold(); + String styleGuideDetailsTypeface_regular(); + } +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.java similarity index 92% rename from java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.java rename to java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.java index 3d4f769b33b..7b570db82ce 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.java +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.java @@ -1,4 +1,4 @@ -package com.sap.sse.gwt.theme.client.showcase.sapfooter; +package com.sap.sse.gwt.theme.client.showcase.components; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.ui.xml similarity index 77% rename from java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.ui.xml rename to java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.ui.xml index fd55cb46f05..2b993681cc1 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapfooter/SAPFooterShowcase.ui.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPFooterShowcase.ui.xml @@ -3,22 +3,16 @@ xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:ux="urn:import:com.sap.sse.gwt.theme.client.showcase" xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.sapfooter"> - - .showcase_content { - width: 100%; - display: block; - margin: 10px; - } - + - + A SAP footer.

Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.sapfooter"

- + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.java similarity index 93% rename from java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.java rename to java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.java index 8e16cf7a833..aa07434270e 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.java +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.java @@ -1,4 +1,4 @@ -package com.sap.sse.gwt.theme.client.showcase.sapheader; +package com.sap.sse.gwt.theme.client.showcase.components; import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.ui.xml similarity index 78% rename from java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml rename to java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.ui.xml index cd72d089a03..ccf44dc116c 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/sapheader/SAPHeaderShowcase.ui.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/SAPHeaderShowcase.ui.xml @@ -3,22 +3,16 @@ xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:ux="urn:import:com.sap.sse.gwt.theme.client.showcase" xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.sapheader"> - - .showcase_content { - width: 100%; - display: block; - margin: 10px; - } - + - + A simple header with SAP logo and title.

Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.sapheader"

- + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/showcase.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/showcase.gss new file mode 100644 index 00000000000..1a9a5c4b88e --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/showcase.gss @@ -0,0 +1,78 @@ +.showcaseHeader { + width: 100%; + display: block; + margin: 5px; +} + +.showcaseContent { + width: 100%; + display: block; + margin: 5px; +} + +.styleGuideHeader { + width: 100%; + display: block; + margin: 5px; +} + +.styleGuideContent { + width: 100%; + display: block; + margin: 5px; +} + +.styleGuideRow { + clear: both; + margin-top: 50px; + overflow: hidden; +} + +.styleGuideRowDescription { + float: left; + font-weight: 700; +} + +.styleGuideRowDescriptionTitle { + float: left; +} + +.styleGuideRowContent { + float: left; +} + +.styleGuideRowCode { + background-color: #EEEEEE; + border: 1px solid #CCCCCC; + display: none; + float: left; + font-family: "Lucida Console",Monaco,"Courier New",Courier,monospace; + margin-top: 20px; + padding: 20px 10px; +} + +.styleGuideRowDescription_codeLink { + color: #999999; + display: none; + float: right; + font-family: monospace; + font-size: 12px; + font-weight: 400; + line-height: 23px; + margin-right: 25px; +} + +.styleGuideDetailsTypeface_semibold { + font-size: 22px; + font-weight: 600; +} + +.styleGuideDetailsTypeface_regular { + font-size: 22px; +} + +.styleGuideDetailsTypeface_bold { + font-size: 22px; + font-weight: 700; +} + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/AbstractStyleguideComposite.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/AbstractStyleguideComposite.java new file mode 100644 index 00000000000..45919a4a389 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/AbstractStyleguideComposite.java @@ -0,0 +1,13 @@ +package com.sap.sse.gwt.theme.client.showcase.styleguide; + +import com.google.gwt.user.client.ui.Composite; +import com.sap.sse.gwt.theme.client.resources.ThemeResources; +import com.sap.sse.gwt.theme.client.showcase.ShowCaseResources; + +public class AbstractStyleguideComposite extends Composite { + public AbstractStyleguideComposite() { + ThemeResources.INSTANCE.mediaCss().ensureInjected(); + ThemeResources.INSTANCE.mainCss().ensureInjected(); + ShowCaseResources.INSTANCE.css().ensureInjected(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.java new file mode 100644 index 00000000000..f6162d6643f --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.java @@ -0,0 +1,19 @@ +package com.sap.sse.gwt.theme.client.showcase.styleguide; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Widget; + +public class TypefaceStyleguide extends AbstractStyleguideComposite { + + private static TypefaceStyleguideUiBinder uiBinder = GWT.create(TypefaceStyleguideUiBinder.class); + + interface TypefaceStyleguideUiBinder extends UiBinder { + } + + public TypefaceStyleguide() { + super(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.ui.xml new file mode 100644 index 00000000000..64479c7583c --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/styleguide/TypefaceStyleguide.ui.xml @@ -0,0 +1,44 @@ + + + + + + + + Typeface styleguide + + + + +
+
+
+
REGULAR
+ </> +
+
Grumpy wizards make toxic brew for the evil queen and jack.
+
Code und sooo
+
+
+
+
SEMIBOLD
+ </> +
+
Grumpy wizards make toxic brew for the evil queen and jack.
+
Code und sooo
+
+
+
+
BOLD
+ </> +
+
Grumpy wizards make toxic brew for the evil queen and jack.
+
Code und sooo
+
+
+
+
+
+
\ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/styleguide/typeface/typeface.html b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/styleguide/typeface/typeface.html deleted file mode 100644 index 5e580d7c2be..00000000000 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/styleguide/typeface/typeface.html +++ /dev/null @@ -1,24 +0,0 @@ -
-
Typeface Open Sans
-
-
-
REGULAR
</> -
-
Grumpy wizards make toxic brew for the evil queen and jack.
-
Code und sooo
-
-
-
-
SEMIBOLD
</> -
-
Grumpy wizards make toxic brew for the evil queen and jack.
-
Code und sooo
-
-
-
-
BOLD
</> -
-
Grumpy wizards make toxic brew for the evil queen and jack.
-
Code und sooo
-
-
From 102443c50add9aa9a1d6fc083498c7bb77cc7eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bro=C3=9F?= Date: Thu, 5 Feb 2015 21:46:10 +0100 Subject: [PATCH 024/264] Revert to old way of accessing Competitors, has to be fixed later --- .../domain/leaderboard/impl/RegattaLeaderboardImpl.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java index 5d4d4fd6f8c..91622a00623 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java @@ -101,9 +101,4 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R public IsRegattaLike getRegattaLike() { return regatta; } - - @Override - public Iterable getAllCompetitors() { - return regatta.getAllCompetitors(); - } } From 7900b278053608e1987101cd6e92c216e4a76dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bro=C3=9F?= Date: Thu, 5 Feb 2015 23:12:44 +0100 Subject: [PATCH 025/264] First draft of E-Mail sending service --- .../domain/common/dto/CompetitorDTOImpl.java | 4 + .../domain/base/impl/CompetitorImpl.java | 3 + .../impl/RegattaLeaderboardImpl.java | 19 +++++ .../META-INF/MANIFEST.MF | 2 + .../gwt/ui/adminconsole/CompetitorPanel.java | 18 ++++- .../sailing/gwt/ui/client/SailingService.java | 3 + .../gwt/ui/client/SailingServiceAsync.java | 4 +- .../sailing/gwt/ui/client/StringMessages.java | 2 + .../gwt/ui/client/StringMessages.properties | 4 +- .../ui/client/StringMessages_de.properties | 4 +- .../ui/client/StringMessages_ru.properties | 4 +- .../gwt/ui/server/SailingServiceImpl.java | 75 +++++++++++++++++++ 12 files changed, 134 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java index c245de77881..88691f0197c 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java @@ -143,6 +143,10 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial @Override public boolean hasEmail() { + if (email == null){ + return false; + } + return !email.isEmpty(); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java index 79d1f7768df..710d6fadc62 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java @@ -126,6 +126,9 @@ public class CompetitorImpl implements DynamicCompetitor { } public boolean hasEmail(){ + if (email == null){ + return false; + } return !email.isEmpty(); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java index 91622a00623..04219964e6f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.leaderboard.impl; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import com.sap.sailing.domain.base.Competitor; @@ -97,6 +98,24 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R } } + @Override + public Iterable getAllCompetitors() { + //FIXME: this is a dirty hack for now, until I know, why the tracked competitors do not appear on the regattaLog.getAllCompetitors! + + HashSet competitors = new HashSet(); + + + for(Competitor competitor : super.getAllCompetitors()){ + competitors.add(competitor); + } + + for(Competitor competitor : regatta.getAllCompetitors()){ + competitors.add(competitor); + } + + return competitors; + } + @Override public IsRegattaLike getRegattaLike() { return regatta; diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 0da4d4e7cd2..3fa3087b700 100755 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -52,6 +52,8 @@ Import-Package: com.sap.sailing.polars, com.sap.sailing.polars.factory, com.sap.sailing.polars.regression, javax.jms;version="1.1.0", + javax.mail;version="1.4.0", + javax.mail.internet;version="1.4.0", javax.servlet;version="3.1.0", javax.servlet.descriptor;version="3.1.0", javax.servlet.http;version="3.1.0", diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java index 7fbf7776214..6be3be217ab 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java @@ -90,12 +90,22 @@ public class CompetitorPanel extends SimplePanel { inviteCompetitorsButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - //TODO: check, whether all have an email attached and send email - Iterable Competitors = competitorTable.getAllCompetitors(); - boolean emailProvidedForAll = isEmailProvidedForAll(Competitors); + Iterable competitors = competitorSelectionModel.getSelectedSet(); + boolean emailProvidedForAll = isEmailProvidedForAll(competitors); if (emailProvidedForAll){ - Window.alert("Sending successfull"); + sailingService.sendInvitationEmailToCompetitors(competitors, new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + Window.alert(stringMessages.sendingMailsFailed()+caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + Window.alert(stringMessages.sendingMailsSuccessfull()); + } + }); } else { Window.alert(stringMessages.notAllCompetitorsProvideEmail()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 4049e8b4021..df9eb3cdaee 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -90,6 +90,7 @@ import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.search.KeywordQuery; +import com.sap.sse.security.shared.MailException; /** * The client side stub for the RPC service. Usually, when a null date is passed to @@ -599,4 +600,6 @@ public interface SailingService extends RemoteService { * @throws IllegalArgumentException if the property with name {@code propertyName} doesn't exist for the service */ void setFileStorageServiceProperties(String serviceName, Map properties); + + void sendInvitationEmailToCompetitors(Iterable competitors) throws MailException; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 22cad399296..27b5a9cf0f0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -696,6 +696,8 @@ public interface SailingServiceAsync extends BuildVersionRetriever { void setActiveFileStorageService(String serviceName, AsyncCallback callback); - void getActiveFileStorageServiceName(AsyncCallback callback); + void getActiveFileStorageServiceName(AsyncCallback callback); + + void sendInvitationEmailToCompetitors(Iterable competitors, AsyncCallback callback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 663824b7698..be59e9c032b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1141,4 +1141,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String pleaseEnterAnEmail(); String inviteCompetitors(); String notAllCompetitorsProvideEmail(); + String sendingMailsFailed(); + String sendingMailsSuccessfull(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index e6d89a9be83..e2e78125a17 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1123,4 +1123,6 @@ fileStorage=File Storage email=E-Mail pleaseEnterAnEmail=Please enter an E-Mail. inviteCompetitors=Invite competitors -notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail \ No newline at end of file +notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail +sendingMailsFailed="Sending Mails failed: " +sendingMailsSuccessfull="Successfully sent E-Mails" \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 136cd23dc00..897f76babff 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1112,4 +1112,6 @@ fileStorage=Datei Speicherung email=E-Mail pleaseEnterAnEmail=Bitte geben Sie eine E-Mail ein. inviteCompetitors=Teilnehmer einladen -notAllCompetitorsProvideEmail=Nicht alle Teilnehmer haben eine E-Mail angegeben \ No newline at end of file +notAllCompetitorsProvideEmail=Nicht alle Teilnehmer haben eine E-Mail angegeben +sendingMailsFailed="Versenden der E-Mails fehlgeschlagen: " +sendingMailsSuccessfull="Versenden der E-Mails war erfolgreich!" \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index da87c21ee55..6878b28d0bb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1060,4 +1060,6 @@ fileStorage=File Storage email=�маль pleaseEnterAnEmail=Пожалуй�та, введите �маль inviteCompetitors=приглашать Уча�тники -notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail \ No newline at end of file +notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail +sendingMailsFailed="Sending Mails failed: " +sendingMailsSuccessfull="Successfully sent E-Mails" \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 98fcb2ce790..3073fd917ec 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1,5 +1,7 @@ package com.sap.sailing.gwt.ui.server; +import java.io.File; +import java.io.FileReader; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; @@ -28,6 +30,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Properties; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; @@ -46,6 +49,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; +import javax.mail.MessagingException; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMessage.RecipientType; import javax.servlet.ServletContext; import org.apache.http.client.ClientProtocolException; @@ -54,6 +64,7 @@ import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; +import com.google.gwt.user.client.Window; import com.sap.sailing.domain.abstractlog.AbstractLog; import com.sap.sailing.domain.abstractlog.MultiLogAnalyzer; import com.sap.sailing.domain.abstractlog.Revokable; @@ -428,6 +439,7 @@ import com.sap.sse.replication.ReplicationFactory; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.ReplicationService; import com.sap.sse.replication.impl.ReplicaDescriptor; +import com.sap.sse.security.shared.MailException; import com.sapsailing.xrr.structureimport.eventimport.RegattaJSON; /** @@ -493,6 +505,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final SwissTimingReplayService swissTimingReplayService; private final QuickRanksLiveCache quickRanksLiveCache; + + private final Properties mailProperties; + public SailingServiceImpl() { BundleContext context = Activator.getDefault(); Activator activator = Activator.getInstance(); @@ -543,6 +558,20 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S /* maximumPoolSize */ THREAD_POOL_SIZE, /* keepAliveTime */ 60, TimeUnit.SECONDS, /* workQueue */ new LinkedBlockingQueue()); + + final String jettyHome = System.getProperty("jetty.home", "configuration"); + final File propertiesDir = new File(jettyHome).getParentFile(); + File propertiesfile = new File(propertiesDir, "security.properties"); + mailProperties = new Properties(); + try { + mailProperties.load(new FileReader(propertiesfile)); + } catch (IOException ioe) { + try { + logger.log(Level.SEVERE, "Couldn't read security properties from "+propertiesfile.getCanonicalPath(), ioe); + } catch (Exception e) { + logger.log(Level.SEVERE, "Couldn't read security properties and could net fetch canonical path"); + } + } } /** @@ -5482,4 +5511,50 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return null; } } + + + private class SMTPAuthenticator extends javax.mail.Authenticator { + + public PasswordAuthentication getPasswordAuthentication() { + String username = mailProperties.getProperty("mail.smtp.user"); + String password = mailProperties.getProperty("mail.smtp.password"); + return new PasswordAuthentication(username, password); + } + } + + @Override + public void sendInvitationEmailToCompetitors(Iterable competitors) throws MailException { + if (!(this.mailProperties != null && this.mailProperties.containsKey("mail.transport.protocol"))) { + Window.alert("Check this, not implemented yet!"); + return; + } + + StringBuilder occuredExceptions = new StringBuilder(); + + for (CompetitorDTO competitor : competitors){ + final String toAddress = competitor.getEmail(); + if (toAddress != null) { + Session session = Session.getInstance(this.mailProperties, new SMTPAuthenticator()); + MimeMessage msg = new MimeMessage(session); + try { + msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); + msg.setSubject("Invitation to a SAP Sailing Analytics event"); //FIXME: I18N? + msg.setContent("Test", "text/plain"); + msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); + Transport ts = session.getTransport(); + ts.connect(); + ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); + ts.close(); + logger.info("mail sent to competitor " + competitor.getName() + " with e-mail address " + toAddress); + } catch (MessagingException e) { + logger.log(Level.SEVERE, "Error trying to send mail to user " + competitor.getName() + + " with e-mail address " + toAddress, e); + occuredExceptions.append(e.getMessage()+"\r\n"); + } + } + } + if (!(occuredExceptions.length() == 0)){ + throw new MailException(occuredExceptions.toString()); + } + } } From ccc9fabb00149e51c80e66ae165db9bb01781a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bro=C3=9F?= Date: Sun, 8 Feb 2015 11:32:01 +0100 Subject: [PATCH 026/264] Add ZXing Barcode Library --- .../features/target-base/feature.xml | 8 ++++++++ ...apache.servicemix.bundles.zxing-3.0.1_1.jar | Bin 0 -> 587612 bytes 2 files changed, 8 insertions(+) create mode 100644 java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.0.1_1.jar diff --git a/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml b/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml index 2e83e1b1984..bd3567e3504 100755 --- a/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml +++ b/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml @@ -137,4 +137,12 @@ version="1.9.4" unpack="false"/> + + + diff --git a/java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.0.1_1.jar b/java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.0.1_1.jar new file mode 100644 index 0000000000000000000000000000000000000000..f437611d630b9eb5961f9a8efd185fcbae6de537 GIT binary patch literal 587612 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
9yJ}Y<7?MM5G+{=Fnq@UaXAuO~w>78l+=7B% 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
++H6Jf(DtrUQQkg-nu9 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%0D;xh*CHip;% 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%0DFOxW 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 Date: Mon, 9 Feb 2015 11:12:37 +0100 Subject: [PATCH 027/264] Added new regression type --- .../AnyOrderLeastSquaresRegressionTest.java | 52 ++++++ .../polars/impl/PolarDataServiceImpl.java | 3 - .../impl/AnyOrderLeastSquaresRegression.java | 152 ++++++++++++++++++ 3 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java new file mode 100644 index 00000000000..0e7a728084b --- /dev/null +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java @@ -0,0 +1,52 @@ +package com.sap.sailing.polars.regression.test; + +import static org.hamcrest.Matchers.closeTo; +import static org.junit.Assert.assertThat; + +import java.util.Random; + +import org.junit.Test; + +import com.sap.sailing.polars.regression.impl.AnyOrderLeastSquaresRegression; + +public class AnyOrderLeastSquaresRegressionTest { + + + private static final double ERROR = 0.01; + + @Test + public void testNonIncrementalRegression() { + AnyOrderLeastSquaresRegression regression = new AnyOrderLeastSquaresRegression(3); + Random random = new Random(1); + for (int i = 0; i < 100000; i++) { + double x = random.nextDouble() * 40; + regression.addData(function(x) + (random.nextDouble() - 0.5), x); + } + + long startTime = System.nanoTime(); + double[] coeffs = regression.getCoefficiants(); + System.out.println("First calculation " + (System.nanoTime() - startTime) + "ns"); + + startTime = System.nanoTime(); + coeffs = regression.getCoefficiants(); + System.out.println("No calculation " + (System.nanoTime() - startTime) + "ns"); + double x = random.nextDouble() * 40; + regression.addData(function(x) + (random.nextDouble() - 0.5), x); + startTime = System.nanoTime() ; + coeffs = regression.getCoefficiants(); + System.out.println("Second calculation " + (System.nanoTime() - startTime) + "ns"); + + assertThat(coeffs[0], closeTo(-2.1309, ERROR)); + assertThat(coeffs[1], closeTo(1.5213, ERROR)); + assertThat(coeffs[2], closeTo(-0.0373, ERROR)); + assertThat(coeffs[3], closeTo(0.0003, ERROR)); + } + + private double function(double x) { + return 0.0003*x*x*x-0.0373*x*x+1.5213*x -2.1309; + } + + + + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java index 3c9528b8c76..db6e387004d 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java @@ -43,9 +43,6 @@ public class PolarDataServiceImpl implements PolarDataService { private final PolarDataMiner polarDataMiner; - - - public PolarDataServiceImpl(Executor executor) { this.polarDataMiner = new PolarDataMiner(); } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java new file mode 100644 index 00000000000..2c31df3bb44 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java @@ -0,0 +1,152 @@ +package com.sap.sailing.polars.regression.impl; + +import com.sap.sse.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; + +/** + * Thread safe implementation of the Least Squares regression for any order of result polynomial. + * + * {@link #addData(double, double)} will incrementally update the simultaneous equations needed for the calculation of + * the result coefficients. + * + * {@link #getCoefficiants()} will do the solving of the equations, if new data has been added since the last + * calculation. + * + * @author D054528 + * + */ +public class AnyOrderLeastSquaresRegression { + + private final double aMatrix[][]; + private final double bMatrix[]; + private final double term[]; + private final int nOrder; + + private boolean needsCoeffUpdate = true; + private double[] coeffs; + + private final NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("LeastSquaresLock", true); + + /** + * + * @param nOrder order of the polynomial you want to fit over the data. + */ + public AnyOrderLeastSquaresRegression(int nOrder) { + this.nOrder = nOrder; + aMatrix = new double[nOrder + 1][nOrder + 1]; + bMatrix = new double[nOrder + 1]; + term = new double[nOrder + 1]; + } + + /** + * Add data to the regression. This will NOT trigger a calculation of the coefficiants, but only update the matrices + * that form the simultaneous equations. + * + * Use {@link #getCoefficiants()} to retrieve the coefficiants of the fit. + * + * @param yValue + * @param xValue + */ + public void addData(double yValue, double xValue) { + LockUtil.lockForWrite(lock); + try { + needsCoeffUpdate = true; + // sum the y values + bMatrix[0] += yValue; + // sum the x power values + double xpower = 1; + for (int j = 0; j < nOrder + 1; j++) { + term[j] = xpower; + aMatrix[0][j] += xpower; + xpower = xpower * xValue; + } + // now set up the rest of rows in the matrix - multiplying each row by each term + for (int j = 1; j < nOrder + 1; j++) { + bMatrix[j] += yValue * term[j]; + for (int k = 0; k < bMatrix.length; k++) { + aMatrix[j][k] += term[j] * term[k]; + } + } + } finally { + LockUtil.unlockAfterWrite(lock); + } + } + + /** + * If the coefficiants have been requested before, and not data was added, the coefficiants will simply be returned. + * If data was added since the last time this method was called, the calculation will take place. + * + * The calculation is standard gaussian solving of the simultaneous equations. It has the complexity (nOrder + 1)^3 + * + * @return Coefficiants a[] of the polynomial function. Sorted by power => a[0]*x^0 to a[n]*x^n. + */ + public double[] getCoefficiants() { + if (needsCoeffUpdate) { + double[] coeffs = gauss(); + LockUtil.lockForWrite(lock); + try { + this.coeffs = coeffs; + needsCoeffUpdate = false; + } finally { + LockUtil.unlockAfterWrite(lock); + } + } + LockUtil.lockForRead(lock); + double[] result; + try { + result = this.coeffs; + } finally { + LockUtil.unlockAfterRead(lock); + } + return result; + } + + /** + * a standard gaussian technique for / solving simultaneous equations of the form: |A| = |B| * |C| where we / know + * the values of |A| and |B|, and we are solving for the coefficients / in |C| + */ + private double[] gauss() { + double aCopy[][] = new double[aMatrix.length][aMatrix[0].length]; + double bCopy[] = new double[bMatrix.length]; + double pivot; + double mult; + double top; + int n = bCopy.length; + double coef[] = new double[n]; + + // copy over the array values - inplace solution changes values + LockUtil.lockForRead(lock); + try { + for (int i = 0; i < aMatrix.length; i++) { + for (int j = 0; j < aMatrix[i].length; j++) { + aCopy[i][j] = aMatrix[i][j]; + } + bCopy[i] = bMatrix[i]; + } + } finally { + LockUtil.unlockAfterRead(lock); + } + + for (int j = 0; j < (n - 1); j++) { + pivot = aCopy[j][j]; + for (int i = j + 1; i < n; i++) { + mult = aCopy[i][j] / pivot; + for (int k = j + 1; k < n; k++) { + aCopy[i][k] = aCopy[i][k] - mult * aCopy[j][k]; + } + bCopy[i] = bCopy[i] - mult * bCopy[j]; + } + } + + coef[n - 1] = bCopy[n - 1] / aCopy[n - 1][n - 1]; + for (int i = n - 2; i >= 0; i--) { + top = bCopy[i]; + for (int k = i + 1; k < n; k++) { + top = top - aCopy[i][k] * coef[k]; + } + coef[i] = top / aCopy[i][i]; + } + return coef; + } + +} From b619081830e624a6dbef5edbb22145d3a6c58bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bro=C3=9F?= Date: Mon, 9 Feb 2015 11:19:51 +0100 Subject: [PATCH 028/264] Initial commit of qr code invitation algorithm --- .../impl/RegattaLeaderboardImpl.java | 2 - .../META-INF/MANIFEST.MF | 3 +- .../gwt/ui/adminconsole/CompetitorPanel.java | 74 +++++---- .../EventListCompositeDialog.java | 73 +++++++++ .../sailing/gwt/ui/client/SailingService.java | 5 +- .../gwt/ui/client/SailingServiceAsync.java | 2 +- .../gwt/ui/server/SailingServiceImpl.java | 155 +++++++++++++++--- .../features/target-base/feature.xml | 17 +- 8 files changed, 261 insertions(+), 70 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListCompositeDialog.java diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java index 04219964e6f..a78f6ede725 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java @@ -100,8 +100,6 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R @Override public Iterable getAllCompetitors() { - //FIXME: this is a dirty hack for now, until I know, why the tracked competitors do not appear on the regattaLog.getAllCompetitors! - HashSet competitors = new HashSet(); diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 3fa3087b700..4ff2c22eaa3 100755 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -45,7 +45,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.operationaltransformation, com.sap.sse, com.sap.sse.security.common, - com.sap.sse.filestorage + com.sap.sse.filestorage, + org.apache.servicemix.bundles.zxing Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy Import-Package: com.sap.sailing.polars, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java index 6be3be217ab..8c4483c0758 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; import com.google.gwt.cell.client.FieldUpdater; import com.google.gwt.event.dom.client.ClickEvent; @@ -43,8 +44,8 @@ public class CompetitorPanel extends SimplePanel { this(sailingService, null, stringMessages, errorReporter); } - public CompetitorPanel(final SailingServiceAsync sailingService, String leaderboardName, final StringMessages stringMessages, - final ErrorReporter errorReporter) { + public CompetitorPanel(final SailingServiceAsync sailingService, final String leaderboardName, + final StringMessages stringMessages, final ErrorReporter errorReporter) { super(); this.sailingService = sailingService; this.stringMessages = stringMessages; @@ -79,11 +80,11 @@ public class CompetitorPanel extends SimplePanel { buttonPanel.add(allowReloadButton); Button addCompetitorButton = new Button(stringMessages.add()); addCompetitorButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - openAddCompetitorDialog(); - } - }); + @Override + public void onClick(ClickEvent event) { + openAddCompetitorDialog(); + } + }); buttonPanel.add(addCompetitorButton); final Button inviteCompetitorsButton = new Button(stringMessages.inviteCompetitors()); @@ -92,42 +93,29 @@ public class CompetitorPanel extends SimplePanel { public void onClick(ClickEvent event) { Iterable competitors = competitorSelectionModel.getSelectedSet(); boolean emailProvidedForAll = isEmailProvidedForAll(competitors); - - if (emailProvidedForAll){ - sailingService.sendInvitationEmailToCompetitors(competitors, new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - Window.alert(stringMessages.sendingMailsFailed()+caught.getMessage()); - } - - @Override - public void onSuccess(Void result) { - Window.alert(stringMessages.sendingMailsSuccessfull()); - } - }); + if (emailProvidedForAll) { + openChooseEventDialogAndSendMails(competitors); } else { Window.alert(stringMessages.notAllCompetitorsProvideEmail()); } } private boolean isEmailProvidedForAll(Iterable allCompetitors) { - for (CompetitorDTO competitor : allCompetitors){ - if (!competitor.hasEmail()){ + for (CompetitorDTO competitor : allCompetitors) { + if (!competitor.hasEmail()) { return false; } } - + return true; } }); buttonPanel.add(inviteCompetitorsButton); - - + competitorsPanel.add(buttonPanel); - - //competitor table + // competitor table ImagesBarColumn competitorActionColumn = new ImagesBarColumn( new CompetitorConfigImagesBarCell(stringMessages)); competitorActionColumn.setFieldUpdater(new FieldUpdater() { @@ -151,8 +139,8 @@ public class CompetitorPanel extends SimplePanel { } }); allowReloadButton.setEnabled(!competitorSelectionModel.getSelectedSet().isEmpty()); - - if(leaderboardName != null) { + + if (leaderboardName != null) { refreshCompetitorList(); } } @@ -201,6 +189,34 @@ public class CompetitorPanel extends SimplePanel { }).show(); } + private void openChooseEventDialogAndSendMails(final Iterable competitors) { + EventListCompositeDialog dialog = new EventListCompositeDialog(sailingService, stringMessages, errorReporter, + new DialogCallback>() { + + @Override + public void ok(List eventsToInviteTo) { + sailingService.sendInvitationEmailToCompetitors(competitors, leaderboardName, eventsToInviteTo, new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + Window.alert(stringMessages.sendingMailsFailed()+caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + Window.alert(stringMessages.sendingMailsSuccessfull()); + } + }); + + } + + @Override + public void cancel() { + } + }); + dialog.show(); + } + public void refreshCompetitorList() { competitorTable.refreshCompetitorList(leaderboardName); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListCompositeDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListCompositeDialog.java new file mode 100644 index 00000000000..dd33f41f378 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListCompositeDialog.java @@ -0,0 +1,73 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.List; +import java.util.UUID; + +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.DataEntryDialogWithBootstrap; +import com.sap.sailing.gwt.ui.client.EventSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.EventSelectionModel; +import com.sap.sailing.gwt.ui.client.EventSelectionProvider; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.ErrorReporter; + +public class EventListCompositeDialog extends DataEntryDialogWithBootstrap> implements EventSelectionChangeListener { + protected StringMessages stringMessages; + + private EventSelectionProvider eventSelectionProvider; + private EventListComposite eventListComposite; + private List selectedEvents; + + protected static class EventListValidator implements Validator> { + private StringMessages stringMessages; + + public EventListValidator(StringMessages stringMessages) { + this.stringMessages = stringMessages; + } + + @Override + public String getErrorMessage(List valueToValidate) { + if (valueToValidate.isEmpty()){ + return stringMessages.pleaseSelectAnEvent(); + } + return null; + } + } + + public EventListCompositeDialog(SailingServiceAsync sailingService, StringMessages stringMessages,final ErrorReporter errorReporter, DialogCallback> callback) { + super(stringMessages.event(), null, stringMessages.ok(), stringMessages.cancel(), new EventListValidator(stringMessages), callback); + this.stringMessages = stringMessages; + + eventSelectionProvider = new EventSelectionModel(true); + eventSelectionProvider.addEventSelectionChangeListener(this); + + eventListComposite = new EventListComposite(sailingService, eventSelectionProvider, errorReporter, stringMessages); + eventListComposite.ensureDebugId("EventListComposite"); + } + + @Override + protected List getResult() { + return selectedEvents; + } + + @Override + protected Widget getAdditionalWidget() { + final VerticalPanel panel = new VerticalPanel(); + panel.setWidth("100%"); + Widget additionalWidget = super.getAdditionalWidget(); + if (additionalWidget != null) { + panel.add(additionalWidget); + } + + panel.add(eventListComposite); + + return panel; + } + + @Override + public void onEventSelectionChange(List selectedEvents) { + this.selectedEvents = selectedEvents; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index df9eb3cdaee..39daf42f9e9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -600,6 +600,7 @@ public interface SailingService extends RemoteService { * @throws IllegalArgumentException if the property with name {@code propertyName} doesn't exist for the service */ void setFileStorageServiceProperties(String serviceName, Map properties); - - void sendInvitationEmailToCompetitors(Iterable competitors) throws MailException; + + void sendInvitationEmailToCompetitors(Iterable competitors, String leaderboardName, + List eventsToInviteTo) throws MailException; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 27b5a9cf0f0..a4f214a3c07 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -698,6 +698,6 @@ public interface SailingServiceAsync extends BuildVersionRetriever { void getActiveFileStorageServiceName(AsyncCallback callback); - void sendInvitationEmailToCompetitors(Iterable competitors, AsyncCallback callback); + void sendInvitationEmailToCompetitors(Iterable competitors, String leaderboardName, List eventsToInviteTo, AsyncCallback asyncCallback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 3073fd917ec..1aedbf92028 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1,5 +1,8 @@ package com.sap.sailing.gwt.ui.server; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; import java.io.File; import java.io.FileReader; import java.io.FilterInputStream; @@ -25,6 +28,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -49,22 +53,35 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; +import javax.imageio.ImageIO; +import javax.mail.BodyPart; import javax.mail.MessagingException; +import javax.mail.Multipart; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; +import javax.mail.internet.InternetHeaders; +import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage.RecipientType; +import javax.mail.internet.MimeMultipart; import javax.servlet.ServletContext; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.http.client.ClientProtocolException; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; -import com.google.gwt.user.client.Window; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.sap.sailing.domain.abstractlog.AbstractLog; import com.sap.sailing.domain.abstractlog.MultiLogAnalyzer; import com.sap.sailing.domain.abstractlog.Revokable; @@ -201,6 +218,7 @@ import com.sap.sailing.domain.common.racelog.FlagPole; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice; import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; @@ -442,6 +460,7 @@ import com.sap.sse.replication.impl.ReplicaDescriptor; import com.sap.sse.security.shared.MailException; import com.sapsailing.xrr.structureimport.eventimport.RegattaJSON; + /** * The server side implementation of the RPC service. */ @@ -5523,38 +5542,124 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public void sendInvitationEmailToCompetitors(Iterable competitors) throws MailException { + public void sendInvitationEmailToCompetitors(Iterable competitors, String leaderboardName, List eventsToInviteTo) throws MailException { if (!(this.mailProperties != null && this.mailProperties.containsKey("mail.transport.protocol"))) { - Window.alert("Check this, not implemented yet!"); + logger.severe("Could not find valid email properties to send competitor invitation E-Mail"); return; } + if (eventsToInviteTo.isEmpty()){ + throw new MailException("Please select an event in order to invite competitors"); + } + StringBuilder occuredExceptions = new StringBuilder(); - for (CompetitorDTO competitor : competitors){ - final String toAddress = competitor.getEmail(); - if (toAddress != null) { - Session session = Session.getInstance(this.mailProperties, new SMTPAuthenticator()); - MimeMessage msg = new MimeMessage(session); - try { - msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); - msg.setSubject("Invitation to a SAP Sailing Analytics event"); //FIXME: I18N? - msg.setContent("Test", "text/plain"); - msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); - Transport ts = session.getTransport(); - ts.connect(); - ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); - ts.close(); - logger.info("mail sent to competitor " + competitor.getName() + " with e-mail address " + toAddress); - } catch (MessagingException e) { - logger.log(Level.SEVERE, "Error trying to send mail to user " + competitor.getName() - + " with e-mail address " + toAddress, e); - occuredExceptions.append(e.getMessage()+"\r\n"); + for (UUID eventToInviteTo : eventsToInviteTo){ + for (CompetitorDTO competitor : competitors){ + final String toAddress = competitor.getEmail(); + if (toAddress != null) { + + //generate QR Code of form + //http:///tracking/checkin?event_id=&leaderboard_name=&competitor_id= + Session session = Session.getInstance(this.mailProperties, new SMTPAuthenticator()); + MimeMessage msg = new MimeMessage(session); + try { + msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); + msg.setSubject("Invitation to a SAP Sailing Analytics event"); //FIXME: I18N? + msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); + + MimeBodyPart messageBodyPart = new MimeBodyPart(); + messageBodyPart.setContent("

Please scan the QR-Code with the SAP Sailing Tracking " + + "App to join


", "text/html"); + + String invitationUrl = generateInvitationUrl(eventToInviteTo,leaderboardName,competitor.getIdAsString()); + BodyPart qrCodeBodyPart = createInlineImagePart(createQRImage(invitationUrl)); + + Multipart multipart = new MimeMultipart(); + multipart.addBodyPart(messageBodyPart); + multipart.addBodyPart(qrCodeBodyPart); + + Transport ts = session.getTransport(); + ts.connect(); + ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); + ts.close(); + logger.info("mail sent to competitor " + competitor.getName() + " with e-mail address " + toAddress); + } catch (MessagingException | WriterException | IOException e) { + logger.log(Level.SEVERE, "Error trying to send mail to user " + competitor.getName() + + " with e-mail address " + toAddress, e); + occuredExceptions.append(e.getMessage()+"\r\n"); + } } } - } - if (!(occuredExceptions.length() == 0)){ - throw new MailException(occuredExceptions.toString()); + if (!(occuredExceptions.length() == 0)){ + throw new MailException(occuredExceptions.toString()); + } } } + + private String generateInvitationUrl(UUID eventToInviteTo, String leaderboardName, String competitorId) { + String encodedEventId = com.google.gwt.http.client.URL.encodePathSegment(eventToInviteTo.toString()); + String encodedLeaderboardName = com.google.gwt.http.client.URL.encodePathSegment(leaderboardName.toString()); + + return "http://127.0.0.1/" + DeviceMappingConstants.URL_BASE //FIXME: baseUrlWithTrailingSlash needed + + "?" + DeviceMappingConstants.URL_EVENT_ID + "=" + encodedEventId + + "&" + DeviceMappingConstants.URL_LEADERBOARD_NAME + "=" + encodedLeaderboardName + + "&" + competitorId; + } + + private static ByteArrayOutputStream createQRImage(String qrCodeText) throws WriterException, IOException { + // Create the ByteMatrix for the QR-Code that encodes the given String + Hashtable hintMap = new Hashtable<>(); + hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix byteMatrix = qrCodeWriter.encode(qrCodeText, + BarcodeFormat.QR_CODE, 125, 125, hintMap); + // Make the BufferedImage that are to hold the QRCode + int matrixWidth = byteMatrix.getWidth(); + BufferedImage image = new BufferedImage(matrixWidth, matrixWidth, + BufferedImage.TYPE_INT_RGB); + image.createGraphics(); + + Graphics2D graphics = (Graphics2D) image.getGraphics(); + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, matrixWidth, matrixWidth); + // Paint and save the image using the ByteMatrix + graphics.setColor(Color.BLACK); + + for (int i = 0; i < matrixWidth; i++) { + for (int j = 0; j < matrixWidth; j++) { + if (byteMatrix.get(i, j)) { + graphics.fillRect(i, j, 1, 1); + } + } + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ImageIO.write(image, "jpeg", outputStream); + return outputStream; + } + + private BodyPart createInlineImagePart(ByteArrayOutputStream outputStream) { + + MimeBodyPart imagePart =null; + try + { + Base64 base64 = new Base64(); + byte[] bytearray = base64.encode(outputStream.toByteArray()); + outputStream.close(); + + InternetHeaders headers = new InternetHeaders(); + headers.addHeader("Content-Type", "image/jpeg"); + headers.addHeader("Content-Transfer-Encoding", "base64"); + imagePart = new MimeBodyPart(headers, bytearray); + imagePart.setDisposition(MimeBodyPart.INLINE); + imagePart.setContentID("<qr_code>"); + imagePart.setFileName("qr_code.jpg"); + } + catch(Exception exp) + { + logger.warning("Could not create ImagePart for Competitor invitation"); + } + + return imagePart; + } } diff --git a/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml b/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml index bd3567e3504..403240b3f6d 100755 --- a/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml +++ b/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml @@ -121,7 +121,13 @@ id="org.apache.servicemix.bundles.aws-java-sdk" download-size="0" install-size="0" - version="1.9.8.1" + version="1.9.8.1"/> + + - - - - From 00ed31461738c3fd3b319753b2c6a36b79696112 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Mon, 9 Feb 2015 11:23:21 +0100 Subject: [PATCH 029/264] Started working on integrating new multi order regression Will need to evaluate how good the regression works on the kind of data we add in the polar service. --- .../polars/mining/CubicSpeedRegressions.java | 46 +++++++++++++++++++ .../IncrementalRegressionProcessor.java | 10 ++++ 2 files changed, 56 insertions(+) create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java new file mode 100644 index 00000000000..7aa63bbbd56 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java @@ -0,0 +1,46 @@ +package com.sap.sailing.polars.mining; + +import java.util.HashMap; +import java.util.Map; + +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.polars.regression.impl.AnyOrderLeastSquaresRegression; +import com.sap.sse.common.Util.Pair; + +public class CubicSpeedRegressions { + + private final Map, AnyOrderLeastSquaresRegression> regressions; + + public CubicSpeedRegressions() { + regressions = new HashMap<>(); + for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { + for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { + regressions.put(new Pair(legType, tack), + new AnyOrderLeastSquaresRegression(3)); + } + } + } + + public void addFix(GPSFixMovingWithPolarContext fix) { + final LegType legType; + final Tack tack; + final int roundedAngleDeg = fix.getRoundedTrueWindAngle().getAngleDeg(); + if (roundedAngleDeg < -90) { + legType = LegType.DOWNWIND; + tack = Tack.PORT; + } else if (roundedAngleDeg < 0) { + legType = LegType.UPWIND; + tack = Tack.PORT; + } else if (roundedAngleDeg > 90) { + legType = LegType.DOWNWIND; + tack = Tack.STARBOARD; + } else { // roundedAngleDeg > 0 && roundedAngleDeg <= 90 + legType = LegType.UPWIND; + tack = Tack.STARBOARD; + } + AnyOrderLeastSquaresRegression regression = regressions.get(new Pair(legType, tack)); + regression.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java index 054ad5b5842..714ef4d6cc5 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java @@ -50,6 +50,8 @@ public class IncrementalRegressionProcessor implements Processor availableBoatClasses = new HashSet<>(); private final Map, AverageAngleContainer> averageAngleContainers; + + private final Map cubicRegressions = new HashMap<>(); public IncrementalRegressionProcessor(ClusterGroup speedClusterGroup) { this.speedClusterGroup = speedClusterGroup; @@ -81,6 +83,7 @@ public class IncrementalRegressionProcessor implements Processor> windWithConfidenceForSpeed = windSpeed; double confidenceForWindSpeed = windWithConfidenceForSpeed.getConfidence(); double confidenceForWindBearing = angleToTheWind.getConfidence(); @@ -90,6 +93,13 @@ public class IncrementalRegressionProcessor implements Processor element, WindWithConfidence> windSpeed) { From bc21858562a6229412a36262b4d63eaba378df44 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Mon, 9 Feb 2015 11:43:28 +0100 Subject: [PATCH 030/264] Added option for using the regression for polar speeds --- .../sailing/gwt/ui/server/SailingServiceImpl.java | 8 ++++---- .../polars/test/PolarSheetAnalyzerTest.java | 8 ++++---- .../com/sap/sailing/polars/PolarDataService.java | 5 +++-- .../polars/analysis/PolarSheetAnalyzer.java | 2 +- .../analysis/impl/PolarSheetAnalyzerImpl.java | 11 ++++++----- .../sailing/polars/impl/PolarDataServiceImpl.java | 4 ++-- .../polars/mining/CubicSpeedRegressions.java | 9 +++++++++ .../mining/IncrementalRegressionProcessor.java | 15 ++++++++++++--- .../sap/sailing/polars/mining/PolarDataMiner.java | 4 ++-- .../server/gateway/jaxrs/api/PolarResource.java | 2 +- .../sailing/simulator/impl/PolarDiagramGPS.java | 8 ++++---- .../simulator/impl/PolarDiagramGPSDirect.java | 8 ++++---- 12 files changed, 52 insertions(+), 32 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 8ad3755d8cd..7b939506024 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -5370,7 +5370,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S try { SpeedWithBearingWithConfidence averageUpwindStarboardMovingAverage = getService() .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.UPWIND, Tack.STARBOARD); + LegType.UPWIND, Tack.STARBOARD, false); pointsForUpwindStarboardAverageSpeedMovingAverage.add(new Pair(windInKnots, averageUpwindStarboardMovingAverage.getObject().getKnots())); @@ -5383,7 +5383,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S try { SpeedWithBearingWithConfidence averageUpwindPortMovingAverage = getService() .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.UPWIND, Tack.PORT); + LegType.UPWIND, Tack.PORT, false); pointsForUpwindPortAverageSpeedMovingAverage.add(new Pair(windInKnots, averageUpwindPortMovingAverage.getObject().getKnots())); @@ -5396,7 +5396,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S try { SpeedWithBearingWithConfidence averageDownwindStarboardMovingAverage = getService() .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.DOWNWIND, Tack.STARBOARD); + LegType.DOWNWIND, Tack.STARBOARD, false); pointsForDownwindStarboardAverageSpeedMovingAverage.add(new Pair(windInKnots, averageDownwindStarboardMovingAverage.getObject().getKnots())); @@ -5409,7 +5409,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S try { SpeedWithBearingWithConfidence averageDownwindPortMovingAverage = getService() .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.DOWNWIND, Tack.PORT); + LegType.DOWNWIND, Tack.PORT, false); pointsForDownwindPortAverageSpeedMovingAverage.add(new Pair(windInKnots, averageDownwindPortMovingAverage.getObject().getKnots())); diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java index b0549f036cb..86959748977 100755 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java @@ -32,25 +32,25 @@ public class PolarSheetAnalyzerTest { BoatClass boatClass = mock(BoatClass.class); SpeedWithBearingWithConfidence result = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.UPWIND, Tack.STARBOARD); + new KnotSpeedImpl(14), LegType.UPWIND, Tack.STARBOARD, false); assertThat(result.getObject().getKnots(), closeTo(8.5, 0.1)); assertThat(result.getObject().getBearing().getDegrees(), closeTo(50, 0.1)); assertThat(result.getConfidence(), closeTo(0.7, 0.1)); SpeedWithBearingWithConfidence result2 = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.STARBOARD); + new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.STARBOARD, false); assertThat(result2.getObject().getKnots(), closeTo(14.2, 0.1)); assertThat(result2.getObject().getBearing().getDegrees(), closeTo(142.8, 0.1)); assertThat(result2.getConfidence(), closeTo(0.6, 0.1)); SpeedWithBearingWithConfidence result3 = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.UPWIND, Tack.PORT); + new KnotSpeedImpl(14), LegType.UPWIND, Tack.PORT, false); assertThat(result3.getObject().getKnots(), closeTo(8.6, 0.1)); assertThat(result3.getObject().getBearing().getDegrees(), closeTo(-49.5, 0.1)); assertThat(result3.getConfidence(), closeTo(0.7, 0.1)); SpeedWithBearingWithConfidence result4 = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.PORT); + new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.PORT, false); assertThat(result4.getObject().getKnots(), closeTo(13.5, 0.1)); assertThat(result4.getObject().getBearing().getDegrees(), closeTo(-143.6, 0.1)); assertThat(result4.getConfidence(), closeTo(0.25, 0.1)); diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java index f11d2fc38cb..4a26334a7fc 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java @@ -29,7 +29,7 @@ import com.sap.sse.common.Util.Pair; * This service uses a {@link PolarSheetAnalyzer} for more advanced analysis. Its methods are facaded in this interface for * central access.

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�UwK#-$3K#NyRfli9VxvyDrYhWtDW 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 XBfrql@BptJ9rBns8JZWPUDf>`Z{>9a diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target index 8156a153a20..946492ff276 100755 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target @@ -1,5 +1,5 @@ - + @@ -118,6 +118,7 @@ + @@ -226,7 +227,6 @@ - From 9417921af8c2fba0197a46d23e79bd70bd7327dd Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 9 Feb 2015 16:48:43 +0100 Subject: [PATCH 035/264] added an unstyled loading indicator component --- .../loadingindicator/LoadingIndicator.css | 2 ++ .../loadingindicator/LoadingIndicator.java | 19 +++++++++++++++ .../loadingindicator/LoadingIndicator.ui.xml | 9 ++++++++ .../LoadingIndicatorResources.java | 16 +++++++++++++ .../gwt/theme/client/showcase/MainPage.ui.xml | 3 +++ .../components/LoadingIndicatorShowcase.java | 23 +++++++++++++++++++ .../LoadingIndicatorShowcase.ui.xml | 20 ++++++++++++++++ 7 files changed, 92 insertions(+) create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.css create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.ui.xml diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.css b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.css new file mode 100644 index 00000000000..3251d4bb461 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.css @@ -0,0 +1,2 @@ +.loadingIndicator { +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.java new file mode 100644 index 00000000000..f385113684f --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.java @@ -0,0 +1,19 @@ +package com.sap.sse.gwt.theme.client.component.loadingindicator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class LoadingIndicator extends Composite { + private static LoadingIndicatorUiBinder uiBinder = GWT.create(LoadingIndicatorUiBinder.class); + + interface LoadingIndicatorUiBinder extends UiBinder { + } + + public LoadingIndicator() { + LoadingIndicatorResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml new file mode 100644 index 00000000000..506bb27b6de --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml @@ -0,0 +1,9 @@ + + + + + + Loading... + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java new file mode 100644 index 00000000000..683743fdc7d --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java @@ -0,0 +1,16 @@ +package com.sap.sse.gwt.theme.client.component.loadingindicator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface LoadingIndicatorResources extends ClientBundle { + public static final LoadingIndicatorResources INSTANCE = GWT.create(LoadingIndicatorResources.class); + + @Source("LoadingIndicator.css") + LocalCss css(); + + public interface LocalCss extends CssResource { + String loadingIndicator(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml index 794aff49f7e..bd21ccd17ee 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml @@ -14,6 +14,9 @@ + + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.java new file mode 100644 index 00000000000..dcd61d485e6 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.java @@ -0,0 +1,23 @@ +package com.sap.sse.gwt.theme.client.showcase.components; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.loadingindicator.LoadingIndicator; + +public class LoadingIndicatorShowcase extends Composite { + + private static LoadingIndicatorShowcaseUiBinder uiBinder = GWT.create(LoadingIndicatorShowcaseUiBinder.class); + + interface LoadingIndicatorShowcaseUiBinder extends UiBinder { + } + + @UiField + LoadingIndicator loadingIndicator; + + public LoadingIndicatorShowcase() { + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.ui.xml new file mode 100644 index 00000000000..ee194856838 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/LoadingIndicatorShowcase.ui.xml @@ -0,0 +1,20 @@ + + + + + + + A Loading indicator. +

Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.loadingindicator"

+ + + + + + + +

* The interesting methods for a client are {@link #getSpeed(BoatClass, Speed, Bearing, boolean)} if data for a specific angle is - * needed and {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack)} + * needed and {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack, boolean)} * which also returns the average angle for the parameters provided. * * @author Frederik Petersen (D054528) @@ -59,6 +59,7 @@ public interface PolarDataService { * courses yet. Use getSpeed for the desired angle to get rawer information on other courses for now. * @param tack * Polar data can vary depending on the tack the boat is on. + * @param useRegressionForSpeed TODO * @return The estimated average speed of a boat for the supplied parameters with the estimated average bearing to * the true wind and a confidence which consists of the confidences of the wind speed, and boat speed sources (50%) * and a confidence calculated using the amount of underlying fixes (50%). 0 <= confidence < 1
@@ -77,7 +78,7 @@ public interface PolarDataService { * If there is not enough data to supply a value with some kind of significance. */ SpeedWithBearingWithConfidence getAverageSpeedWithBearing(BoatClass boatClass, Speed windSpeed, - LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; + LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException; /** diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java index 07fe69692b2..1f6a67d2d5e 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java @@ -9,5 +9,5 @@ import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; public interface PolarSheetAnalyzer { SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; + Speed windSpeed, LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException; } \ No newline at end of file diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java index d32bcbf1926..8734222f656 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java @@ -31,7 +31,8 @@ public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer { } @Override - public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, Speed windSpeed, LegType legType, Tack tack) + public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, + Speed windSpeed, LegType legType, Tack tack, boolean useRegression) throws NotEnoughDataHasBeenAddedException { final int startAngleInclusive; final int endAngleExclusive; @@ -48,11 +49,11 @@ public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer { startAngleInclusive = 270; endAngleExclusive = 360; } else { - throw new IllegalArgumentException("Leg type must be "+LegType.UPWIND.name()+ - " or "+LegType.DOWNWIND.name()+" but was "+legType.name()); + throw new IllegalArgumentException("Leg type must be " + LegType.UPWIND.name() + " or " + + LegType.DOWNWIND.name() + " but was " + legType.name()); } - SpeedWithBearingWithConfidence speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive, - endAngleExclusive); + SpeedWithBearingWithConfidence speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, + startAngleInclusive, endAngleExclusive); return speedWithBearing; } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java index db6e387004d..13d769aa4e9 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java @@ -91,8 +91,8 @@ public class PolarDataServiceImpl implements PolarDataService { @Override public SpeedWithBearingWithConfidence getAverageSpeedWithBearing(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { - return polarDataMiner.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack); + Speed windSpeed, LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException { + return polarDataMiner.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack, useRegressionForSpeed); } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java index 7aa63bbbd56..3909b11201b 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java @@ -43,4 +43,13 @@ public class CubicSpeedRegressions { regression.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); } + public double estimateSpeedInKnots(LegType legType, Tack tack, double windSpeedInKnots) { + double[] coeffs = regressions.get(new Pair(legType, tack)).getCoefficiants(); + double resultSum = 0; + for (int i = 0; i < coeffs.length; i++) { + resultSum = resultSum + coeffs[i] * Math.pow(windSpeedInKnots, i); + } + return resultSum; + } + } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java index 714ef4d6cc5..436e41d8baa 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java @@ -208,7 +208,7 @@ public class IncrementalRegressionProcessor implements Processor averageSpeedAndCourseOverGround; try { averageSpeedAndCourseOverGround = getAverageSpeedAndCourseOverGround( - boatClass, windSpeed, legType, tack); + boatClass, windSpeed, legType, tack, false); averageBoatSpeedAndCourseForWindSpeed.add(new Pair>(windSpeed, averageSpeedAndCourseOverGround)); } catch (NotEnoughDataHasBeenAddedException e) { @@ -242,10 +242,19 @@ public class IncrementalRegressionProcessor implements Processor getAverageSpeedAndCourseOverGround(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { + Speed windSpeed, LegType legType, Tack tack, boolean useCubicRegressionForSpeed) throws NotEnoughDataHasBeenAddedException { Double averageAngle = averageAngleContainers.get(new Pair<>(legType, tack)).getAverageAngleDeg( boatClass, windSpeed); - return estimateSpeedForAverageAngle(boatClass, windSpeed, averageAngle); + SpeedWithBearingWithConfidence result; + if (useCubicRegressionForSpeed) { + DegreeBearingImpl angleToTheWind = new DegreeBearingImpl(averageAngle); + SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(cubicRegressions.get(boatClass) + .estimateSpeedInKnots(legType, tack, windSpeed.getKnots()), angleToTheWind); + result = new SpeedWithBearingWithConfidenceImpl(speedWithBearing, /* FIXME */0.5, null); + } else { + result = estimateSpeedForAverageAngle(boatClass, windSpeed, averageAngle); + } + return result; } private SpeedWithBearingWithConfidence estimateSpeedForAverageAngle(BoatClass boatClass, Speed windSpeed, diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index bfe976d024f..65ca9ab3e6e 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -218,7 +218,7 @@ public class PolarDataMiner implements PolarSheetAnalyzer { @Override public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { - return incrementalRegressionProcessor.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack); + Speed windSpeed, LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException { + return incrementalRegressionProcessor.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack, useRegressionForSpeed); } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java index c8af43fc78b..4017075d50b 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java @@ -85,7 +85,7 @@ public class PolarResource extends AbstractSailingServerResource { try { PolarDataService service = getService().getPolarDataService(); SpeedWithBearingWithConfidence speedWithBearing = service.getAverageSpeedWithBearing(boatClass, - windSpeed, legType, tack); + windSpeed, legType, tack, false); String resultString = "Speed: " + speedWithBearing.getObject().getKnots() + "kn; Angle: " + speedWithBearing.getObject().getBearing().getDegrees() + "°; Confidence: " + speedWithBearing.getConfidence(); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPS.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPS.java index 3c60ef1b59c..3bd4b43d036 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPS.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPS.java @@ -51,8 +51,8 @@ public class PolarDiagramGPS extends PolarDiagramBase { SpeedWithBearing beatStar; for (int i = 0; i < windSpeeds.size(); i++) { try { - beatPort = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.UPWIND, Tack.PORT).getObject(); - beatStar = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.UPWIND, Tack.STARBOARD).getObject(); + beatPort = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.UPWIND, Tack.PORT, false).getObject(); + beatStar = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.UPWIND, Tack.STARBOARD, false).getObject(); } catch (NotEnoughDataHasBeenAddedException e) { beatPort = null; beatStar = null; @@ -73,8 +73,8 @@ public class PolarDiagramGPS extends PolarDiagramBase { SpeedWithBearing jibeStar; for (int i = 0; i < windSpeeds.size(); i++) { try { - jibePort = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.DOWNWIND, Tack.PORT).getObject(); - jibeStar = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.DOWNWIND, Tack.STARBOARD).getObject(); + jibePort = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.DOWNWIND, Tack.PORT, false).getObject(); + jibeStar = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.DOWNWIND, Tack.STARBOARD, false).getObject(); } catch (NotEnoughDataHasBeenAddedException e) { jibePort = null; jibeStar = null; diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPSDirect.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPSDirect.java index 03057d9a728..35730c1d0bb 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPSDirect.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPSDirect.java @@ -131,11 +131,11 @@ public class PolarDiagramGPSDirect implements PolarDiagram, Serializable { public Bearing[] optimalDirectionsUpwind() { try { if (beatPort == null) { - SpeedWithBearing beatPortRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.UPWIND, Tack.PORT).getObject(); + SpeedWithBearing beatPortRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.UPWIND, Tack.PORT, false).getObject(); beatPort = new KnotSpeedWithBearingImpl(beatPortRelative.getKnots(), this.wind.getBearing().reverse().add(beatPortRelative.getBearing())); } if (beatStar == null) { - SpeedWithBearing beatStarRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.UPWIND, Tack.STARBOARD).getObject(); + SpeedWithBearing beatStarRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.UPWIND, Tack.STARBOARD, false).getObject(); beatStar = new KnotSpeedWithBearingImpl(beatStarRelative.getKnots(), this.wind.getBearing().reverse().add(beatStarRelative.getBearing())); } } catch (NotEnoughDataHasBeenAddedException e) { @@ -156,11 +156,11 @@ public class PolarDiagramGPSDirect implements PolarDiagram, Serializable { public Bearing[] optimalDirectionsDownwind() { try { if (jibePort ==null) { - SpeedWithBearing jibePortRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.DOWNWIND, Tack.PORT).getObject(); + SpeedWithBearing jibePortRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.DOWNWIND, Tack.PORT, false).getObject(); jibePort = new KnotSpeedWithBearingImpl(jibePortRelative.getKnots(), this.wind.getBearing().reverse().add(jibePortRelative.getBearing())); } if (jibeStar == null) { - SpeedWithBearing jibeStarRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.DOWNWIND, Tack.STARBOARD).getObject(); + SpeedWithBearing jibeStarRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.DOWNWIND, Tack.STARBOARD, false).getObject(); jibeStar = new KnotSpeedWithBearingImpl(jibeStarRelative.getKnots(), this.wind.getBearing().reverse().add(jibeStarRelative.getBearing())); } } catch (NotEnoughDataHasBeenAddedException e) { From ff2f66fa330cd3a5a72e618b4485e09c29ad2875 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 9 Feb 2015 12:31:59 +0100 Subject: [PATCH 031/264] added welcome file to web.xml --- java/com.sap.sse.gwt.theme/WEB-INF/web.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/com.sap.sse.gwt.theme/WEB-INF/web.xml b/java/com.sap.sse.gwt.theme/WEB-INF/web.xml index 7afb56e6574..af72bde0bba 100644 --- a/java/com.sap.sse.gwt.theme/WEB-INF/web.xml +++ b/java/com.sap.sse.gwt.theme/WEB-INF/web.xml @@ -19,4 +19,7 @@ + + SSETheme.html + From 9aea39e56cb7384d5f822c151e41a4d096658d2a Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Mon, 9 Feb 2015 14:26:04 +0100 Subject: [PATCH 032/264] Polar sheets panel now displays cubic fit in xy diagram --- .../common/PolarSheetsXYDiagramData.java | 2 + .../impl/PolarSheetsXYDiagramDataImpl.java | 40 +++---- ...WT Remote Profiling (Run-Mode only).launch | 2 +- .../SailingGWT sdm.launch | 2 +- .../PolarSheetsXYDiagramPopupPanel.java | 8 +- .../gwt/ui/server/SailingServiceImpl.java | 109 +++++++----------- .../Security UI sdm.launch | 2 +- 7 files changed, 69 insertions(+), 96 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsXYDiagramData.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsXYDiagramData.java index 45ed2a5328a..0ab886a20b7 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsXYDiagramData.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetsXYDiagramData.java @@ -11,4 +11,6 @@ public interface PolarSheetsXYDiagramData extends Serializable { List> getPointsForAverageConfidence(Tack tack, LegType legType); + List> getPointsForAverageSpeedRegression(Tack tack, LegType legType); + } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsXYDiagramDataImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsXYDiagramDataImpl.java index f91a70af391..8fb3db96bfe 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsXYDiagramDataImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetsXYDiagramDataImpl.java @@ -1,6 +1,5 @@ package com.sap.sailing.domain.common.impl; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,35 +16,18 @@ public class PolarSheetsXYDiagramDataImpl implements PolarSheetsXYDiagramData { } private Map, List>> pointsForAverageSpeedMovingAverage; + + private Map, List>> pointsForAverageSpeedRegression; private Map, List>> pointsForAverageConfidence; - public PolarSheetsXYDiagramDataImpl(List> pointsForUpwindStarboardAverageSpeedMovingAverage, - List> pointsForUpwindStarboardAverageConfidence, - List> pointsForUpwindPortAverageSpeedMovingAverage, - List> pointsForUpwindPortAverageConfidence, - List> pointsForDownwindStarboardAverageSpeedMovingAverage, - List> pointsForDownwindStarboardAverageConfidence, - List> pointsForDownwindPortAverageSpeedMovingAverage, - List> pointsForDownwindPortAverageConfidence) { - pointsForAverageSpeedMovingAverage = new HashMap, List>>(); - pointsForAverageConfidence = new HashMap, List>>(); - pointsForAverageSpeedMovingAverage.put(new Pair(LegType.UPWIND, Tack.STARBOARD), - pointsForUpwindStarboardAverageSpeedMovingAverage); - pointsForAverageConfidence.put(new Pair(LegType.UPWIND, Tack.STARBOARD), - pointsForUpwindStarboardAverageConfidence); - pointsForAverageSpeedMovingAverage.put(new Pair(LegType.UPWIND, Tack.PORT), - pointsForUpwindPortAverageSpeedMovingAverage); - pointsForAverageConfidence.put(new Pair(LegType.UPWIND, Tack.PORT), - pointsForUpwindPortAverageConfidence); - pointsForAverageSpeedMovingAverage.put(new Pair(LegType.DOWNWIND, Tack.STARBOARD), - pointsForDownwindStarboardAverageSpeedMovingAverage); - pointsForAverageConfidence.put(new Pair(LegType.DOWNWIND, Tack.STARBOARD), - pointsForDownwindStarboardAverageConfidence); - pointsForAverageSpeedMovingAverage.put(new Pair(LegType.DOWNWIND, Tack.PORT), - pointsForDownwindPortAverageSpeedMovingAverage); - pointsForAverageConfidence.put(new Pair(LegType.DOWNWIND, Tack.PORT), - pointsForDownwindPortAverageConfidence); + public PolarSheetsXYDiagramDataImpl( + Map, List>> movingAverageSpeedDataLists, + Map, List>> regressionSpeedDataLists, + Map, List>> averageConfidenceDataLists) { + pointsForAverageSpeedMovingAverage = movingAverageSpeedDataLists; + pointsForAverageSpeedRegression = regressionSpeedDataLists; + pointsForAverageConfidence = averageConfidenceDataLists; } @Override @@ -58,4 +40,8 @@ public class PolarSheetsXYDiagramDataImpl implements PolarSheetsXYDiagramData { return pointsForAverageConfidence.get(new Pair(legType, tack)); } + @Override + public List> getPointsForAverageSpeedRegression(Tack tack, LegType legType) { + return pointsForAverageSpeedRegression.get(new Pair(legType, tack)); + } } diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch index ad65be7f6f2..96766428576 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch @@ -56,5 +56,5 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index cb8495408d4..e14a2e3b63a 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -72,5 +72,5 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsXYDiagramPopupPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsXYDiagramPopupPanel.java index 0d7aa86ba3e..61b25de0ef7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsXYDiagramPopupPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetsXYDiagramPopupPanel.java @@ -64,6 +64,12 @@ public class PolarSheetsXYDiagramPopupPanel extends DialogBox { speedSeriesMovingAverage.setPoints(pointsForUpwindStarboardAverageSpeedMovingAverage); chart.addSeries(speedSeriesMovingAverage, false, false); + Point[] pointsForUpwindStarboardAverageSpeedRegression = toPointArray(result.getPointsForAverageSpeedRegression(tack, legType)); + Series speedSeriesRegression = chart.createSeries(); + speedSeriesRegression.setName(tack + " " + legType + " Speed - Regression"); + speedSeriesRegression.setPoints(pointsForUpwindStarboardAverageSpeedRegression); + chart.addSeries(speedSeriesRegression, false, false); + Point[] pointsForUpwindStarboardAverageConfidence = toPointArray(result.getPointsForAverageConfidence(tack, legType)); Series confidenceSeries = chart .createSeries() @@ -77,7 +83,7 @@ public class PolarSheetsXYDiagramPopupPanel extends DialogBox { chart.addSeries(confidenceSeries, false, false); if (!showByDefault) { - speedSeriesMovingAverage.setVisible(false, false); + speedSeriesRegression.setVisible(false, false); confidenceSeries.setVisible(false, false); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 7b939506024..f6f9f48fa37 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -5358,73 +5358,52 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public PolarSheetsXYDiagramData createXYDiagramForBoatClass(String boatClassName) { BoatClass boatClass = getService().getBaseDomainFactory().getOrCreateBoatClass(boatClassName); - List> pointsForUpwindStarboardAverageSpeedMovingAverage = new ArrayList>(); - List> pointsForUpwindStarboardAverageConfidence = new ArrayList>(); - List> pointsForUpwindPortAverageSpeedMovingAverage = new ArrayList>(); - List> pointsForUpwindPortAverageConfidence = new ArrayList>(); - List> pointsForDownwindStarboardAverageSpeedMovingAverage = new ArrayList>(); - List> pointsForDownwindStarboardAverageConfidence = new ArrayList>(); - List> pointsForDownwindPortAverageSpeedMovingAverage = new ArrayList>(); - List> pointsForDownwindPortAverageConfidence = new ArrayList>(); - for (double windInKnots = 0.1; windInKnots < 30; windInKnots = windInKnots + 0.1) { - try { - SpeedWithBearingWithConfidence averageUpwindStarboardMovingAverage = getService() - .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.UPWIND, Tack.STARBOARD, false); - pointsForUpwindStarboardAverageSpeedMovingAverage.add(new Pair(windInKnots, - averageUpwindStarboardMovingAverage.getObject().getKnots())); - - pointsForUpwindStarboardAverageConfidence.add(new Pair(windInKnots, - averageUpwindStarboardMovingAverage.getConfidence())); - } catch (NotEnoughDataHasBeenAddedException e) { - // Do not add a point to the result + Map, List>> movingAverageSpeedDataLists = new HashMap<>(); + Map, List>> regressionSpeedDataLists = new HashMap<>(); + Map, List>> averageConfidenceDataLists = new HashMap<>(); + for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { + for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { + movingAverageSpeedDataLists.put(new Pair(legType, tack), + new ArrayList>()); + regressionSpeedDataLists.put(new Pair(legType, tack), + new ArrayList>()); + averageConfidenceDataLists.put(new Pair(legType, tack), + new ArrayList>()); } - - try { - SpeedWithBearingWithConfidence averageUpwindPortMovingAverage = getService() - .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.UPWIND, Tack.PORT, false); - pointsForUpwindPortAverageSpeedMovingAverage.add(new Pair(windInKnots, - averageUpwindPortMovingAverage.getObject().getKnots())); - - pointsForUpwindPortAverageConfidence.add(new Pair(windInKnots, - averageUpwindPortMovingAverage.getConfidence())); - } catch (NotEnoughDataHasBeenAddedException e) { - // Do not add a point to the result - } - - try { - SpeedWithBearingWithConfidence averageDownwindStarboardMovingAverage = getService() - .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.DOWNWIND, Tack.STARBOARD, false); - pointsForDownwindStarboardAverageSpeedMovingAverage.add(new Pair(windInKnots, - averageDownwindStarboardMovingAverage.getObject().getKnots())); - - pointsForDownwindStarboardAverageConfidence.add(new Pair(windInKnots, - averageDownwindStarboardMovingAverage.getConfidence())); - } catch (NotEnoughDataHasBeenAddedException e) { - // Do not add a point to the result - } - - try { - SpeedWithBearingWithConfidence averageDownwindPortMovingAverage = getService() - .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), - LegType.DOWNWIND, Tack.PORT, false); - pointsForDownwindPortAverageSpeedMovingAverage.add(new Pair(windInKnots, - averageDownwindPortMovingAverage.getObject().getKnots())); - - pointsForDownwindPortAverageConfidence.add(new Pair(windInKnots, - averageDownwindPortMovingAverage.getConfidence())); - } catch (NotEnoughDataHasBeenAddedException e) { - // Do not add a point to the result - } - } - PolarSheetsXYDiagramData data = new PolarSheetsXYDiagramDataImpl( - pointsForUpwindStarboardAverageSpeedMovingAverage, pointsForUpwindStarboardAverageConfidence, - pointsForUpwindPortAverageSpeedMovingAverage, pointsForUpwindPortAverageConfidence, - pointsForDownwindStarboardAverageSpeedMovingAverage, pointsForDownwindStarboardAverageConfidence, - pointsForDownwindPortAverageSpeedMovingAverage, pointsForDownwindPortAverageConfidence); + for (double windInKnots = 0.1; windInKnots < 30; windInKnots = windInKnots + 0.1) { + for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { + for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { + + try { + SpeedWithBearingWithConfidence averageUpwindStarboardMovingAverage = getService() + .getPolarDataService().getAverageSpeedWithBearing(boatClass, + new KnotSpeedImpl(windInKnots), legType, tack, false); + + movingAverageSpeedDataLists.get(new Pair(legType, tack)).add( + new Pair(windInKnots, averageUpwindStarboardMovingAverage.getObject() + .getKnots())); + + SpeedWithBearingWithConfidence averageUpwindStarboardRegression = getService() + .getPolarDataService().getAverageSpeedWithBearing(boatClass, + new KnotSpeedImpl(windInKnots), legType, tack, true); + + regressionSpeedDataLists.get(new Pair(legType, tack)).add( + new Pair(windInKnots, averageUpwindStarboardRegression.getObject() + .getKnots())); + + averageConfidenceDataLists.get(new Pair(legType, tack)).add( + new Pair(windInKnots, averageUpwindStarboardMovingAverage + .getConfidence())); + } catch (NotEnoughDataHasBeenAddedException e) { + // Do not add a point to the result + } + } + } + } + + PolarSheetsXYDiagramData data = new PolarSheetsXYDiagramDataImpl(movingAverageSpeedDataLists, + regressionSpeedDataLists, averageConfidenceDataLists); return data; } diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch index 56e5f53b58d..4655b59b08e 100755 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -45,5 +45,5 @@ - + From dd9b4468d70fa1374a6d1b3b5ee0cf2640d52947 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 15:58:29 +0100 Subject: [PATCH 033/264] add zxing library to target definition --- .../definitions/race-analysis-p2-remote.target | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target index 95ec6374d34..8156a153a20 100755 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target @@ -1,5 +1,5 @@ - + @@ -226,6 +226,7 @@ + From 54b0071b754a778b19465177a5f0e9d474e9c051 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 16:40:18 +0100 Subject: [PATCH 034/264] add zxing library in newer version (including launch configs) --- .../SailingServer (No Proxy).launch | 4 ++-- .../SailingServer (Proxy).launch | 4 ++-- .../features/target-base/feature.xml | 9 ++++----- ...ache.servicemix.bundles.zxing-3.1.0_1.jar} | Bin 587612 -> 587358 bytes .../race-analysis-p2-remote.target | 4 ++-- 5 files changed, 10 insertions(+), 11 deletions(-) rename java/com.sap.sailing.targetplatform.base/plugins/target-base/{org.apache.servicemix.bundles.zxing-3.0.1_1.jar => org.apache.servicemix.bundles.zxing-3.1.0_1.jar} (62%) diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 8d2cd64f937..2bcc7084598 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -29,9 +29,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 4addb3b7f41..5e8e02e681c 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -29,10 +29,10 @@ - + - + diff --git a/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml b/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml index bd3567e3504..08bcf8e68eb 100755 --- a/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml +++ b/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml @@ -138,11 +138,10 @@ unpack="false"/> - diff --git a/java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.0.1_1.jar b/java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.1.0_1.jar similarity index 62% rename from java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.0.1_1.jar rename to java/com.sap.sailing.targetplatform.base/plugins/target-base/org.apache.servicemix.bundles.zxing-3.1.0_1.jar index f437611d630b9eb5961f9a8efd185fcbae6de537..81fd8253c698fbb969e2d1dfc33d47e301ceae0c 100644 GIT binary patch delta 184621 zcmZU41wdQP(l)`}-QA^VaY}J_E$;4aDXzsKxVwAs;$FNsEfgs3UZDKEEmyw#hlHG+ znH^aH6H|YMS^3S4kyOIj; z^U=fLSVIG?Kj!x^6)wqEn8=B$67?@OG6}>wHkh+2jY?M*uD|@HfaIgN-+El6*it3y z#S>(cl6A$OZ$}YysMl*Fk@lb6Uzyx^35$e(r!#V*OZ2*6{T++Mx_7l&Xf13-#}>dk zax5#;yg)o`2FyAeWpbcoX<4v8zn8?n*!=a0oawEcM}|GheF{Pw1sc8AJBM^y+XN?{ zcgiy$B`qWu(_;^3kCxSA`qhTLFty^hmc`2{5v^QnmZ0sqpGODBfZ-KZF5Tx#%}yG03=8erS+^crltj9M79o5?gke@2ds zBqJ;T9W7SV;sj=clkx?RVJ^B_oTWF^jV1mzhqGebL`is(s zLt}D{S>Rg(lN`aWDN`?4X*$-Jq?>|ZPJLmnCzv83zgEL$FDe{YJtS%O#+!CB!cPpK z2k!OOk0H~c%M><_BS(1Rb>Lb_6lv3lg{)yo8pPjkJtc;^065fM_ciuN(*DFK9jde8kT1Nom#(hnJ>#pcvg3 z5qd`$qMSIaPPA1`%Eb#)^fM%G2LJ|G$MM$~eGq7GXL`DolJssd=lFR}udyG$@NsXZ zGyz!fWArM+!spUTnyHVGR~8zC3%&HwPzVEjA~5V+H=#}mQ{-$(=HYRo`8d~PLv>H% zA+oL@K>5{i4T zaZF|3pzQQ}3x2k)vZFf`hp-Z4XR#jz7k_6y(r3+HMN_DyS}>bFiS3pw{7i?Qvg0o7 zSI=z{%4k_^2Encn#%dcJJq^35C)J)bl34AWWf$_PAlcs95afjOg-1fPzsCW= zo=drML>2>|-kdbM;KLOGp33I!YJ(%@Z{vx4`rSOL3Om-@X+5soOmj?9`i4;pbsr3J z!VOq47WDUgn?G$WEk*lt+ezxh>HpUSK~g?0<=?_EuFl^JYCQkH7sYtaKz1ZZ3T%jtb;I)wkC}0J zRELNNE2|12L4V7ja>47?U)T0C3%}3Z>0+eY3qto{B8eAuGT|-=f(`HI3sBO;T!kA> z5-E=|q4M)U{T^?x5K)H@QG6(HKEy{UIj=d54(fAAmG3l?0B3`myn?4r_0mcXEbR|O zM!r|Z#ri`iGroi#(r}vf5ds3D?&b4;V|pR{`=y=4j;G#;MX&=2Ob_RLT0DM?Kifl+ zQ*JgHh1Hjn4KX5sz9wdV>fW;DAWj4o+dkYqk==vL=I?$VsFiB!Mugv4k-^3*CQM|PBL;lcdF>0+fCaXZhb-o_D`ButPd1HN4o89T4rcu zCzdT{zc!)1MV!Y65h>(T>N$8+{60+g+@Lpab)MiW%R~HD3S?OZg5|%b>C4UPes~L` zBtS*W;)Hz2Qd``_bQC|kLObT_WNwKjvPzsXY}RGOjx77-$&XK8TTH#xv}Zj|zvtxC zToZ{|UfDs&1I?xOVKdTfpVX5?e&eEN;`lPa^@`M2IA>>9kO5mDE3ALPEYt{47?2A$*BJ9A6@JdN^D;l_)QDdhl@@4U zIs6#f(5w+er{t$ALbe-2ditewSoMN@%^q$38OE3UpgVSWPK_320vju1wX*l{X8LUf zgl8ZBM?O0d)t(8-%^SoYltFi06O3DM5O5Zo7>$!I?Lf~Gu&~3azMJo51x@=myh@=u z%eW>LoRaKrNXlK5P_ZuT{)5=KxMP9tAU&J0t4{ZpV*U>zwY7Hhb>eeWtg693+vi;j z$O1v}E3W62>&E*)+8TZ1WB@KBipOQ+cAc&y8h(iTS65Gj56p{`H=lz=PnG_eqs;4x z8;URxSP)9s|2tBX!brLQPO8tOflyqsbxC4mLxgH`MYNPJvvu|5^M6Nn5*7KIzw%n- zZ!!MaEc|<*zg%D>sqkYbh2jza5di_;j9oW?U=aZ70-uw!$a{rt3&E@O3@r!D?x*cGfS35kXHckhcpXV^dj1HT@ERsOz>B;h+a zzjL}fIJ1!U$7pCdF{vI-HK#QB919Ef&)I}126 zF2&c6{WP+-7^{lKSEH%nGC#^vA>^(|P3ws621apPrryb?e24t3_BhF2CnO|fdqJ`T zE5G@wbw1|=n!*7+%R)v@B_{z`CC}zQX6&!Zi=^k z9>6c<^I5JAlNBo5D3WQ;O5EBz8Xezk0?FTMHx5^Nn73+{FKG-u+VQEQIIpxF|7_Y>77_X#nFlBwfyH+(nOzn@~SBnp9j!1vHbL$>mcy~Oui};}Z zBwa6kXe80ksNv?BbR4+Y)d#c(wiC^B^Mze~B<+m05Go5lF;TmKyW0^Ga`N4#$k6)Y z?^BodRZOSa&38N1(7V-dH&yoG>+Qr`H19FF3RLw#L-g{gB-J+xBm_!DXLmGvAlywM z*7{O3*7hE;<yhArori9s1_Q6dEVJ@s$PAKX7l zeASpG?1+1F|FwYyt?q=qslKEKGf0GPRYzOJNvT0XU0+LL*`4h0cht!`G+LI){9I9M zu3HvGPa|{!ML86;D>_*`hv=5_(=iUD4}wP}b)FXD9F-7-LhV%nt9h4djxcb)SfK;q4H(++790-$+4^kR~+kvJ#pScX?uG{aVvVv z*cwA?&>bU3SF0>Sb=#sY%MxmBA9Gt#v0U}q0=*|4f2fTvQE#T(?TR%#4UI^@ulN|R zkL!Q8W?zYZy0BV-qvOJ+Eg%2nz)^YewUL)ngIgBo#_0=NI!*sny}pV;T~bI|4w4Ru za*oO#Vyx{AozayC;{O5_vAY@9s&e@Mmehjgs&MN7Zrl*79eJ5mlEU#8kFUza;M~1(&q@!|*#CX`o zsz3yo|14ntP&UHK{w|q~F;Pmdyq=f!F)@Dhub|^PBt_#%{0j>4&sG97MewqA8YD;I z{3$jbl}r4$RkTkpR&6WwU#(75iT%449jN9LJVB#c_4QxopY6mM0RR?+91D-p7*Y%K zcWM1My|Jlf4E6)Az_haq@mq<;1lfLaUsYB;VX|)b5e`ZavF04!kMH~e6a)BE_nixUA5B*Jr+?fHHEvu~>Jx6ZrcIe|zk8`Z?%oDF6H z`=95iIX!gz%44Y}1=xK2yZq9-f&%tCGW@#&BfUVFiyi7^L7i^Jgj%(o!_gom=V|pD zS$2g5c@T+WN`dkXzd{s&>O|9AlHT^aeYdt7W`?L@`h)`YD30uLm?@8G?HnpF&*CJG zUl_KftO|ofid~t`6IjHXOg$4Of_4 zx;-FO?>O`#(hY(G`Wn3z*y!`d$Bmt!Oz5{rsE7s>trAYsPL&&V8xb2ky-YAtFkmE7 z3SQb)u`|ZrN*GT>VZ$@RURxML^1JuFcnICv)1V;zAT?-1$~)Cw5JI<}6KI18+K}On zQIrhvci=`Pj3L#XZto&Ox4{`o5Rm>(vKJ_V>6f&T3G=Ld#uDU+)2Vo7AGCj~ejcBTaVo#xJ=_ZI#H*ROWt0s1@J9iUez2*`M68AOIX&vZx8i-r^+epVPnhV|IG z@hRvn!Z+qSgkB{?$y}BoFk*o0*<279;Tx-0_(pb6F2q;icF6xZ|MJu=ASp5}Cd!EyodxB7Wfkg}|2bz= z_s|v|CM_%a=MY*D1jz|NmmJa%7 zJUFtQ=J{lU>KVTxY+aW*x{JMm)s&F~y`gz4S)ek3cLpkx{lLXu?MuJTHzq&?WnXVZ zjokGN7+xZ_v#CL@LJS$hY?3l#)8}w5%V$ss)b`f)xBxK-*_;F5Va*)vZY1QWNQ`CZ zu;JehX3Tse)eH8J^JN~RhBO8OC6>q_6r@J*Z0!{3>7VhrlZt=I!5)9<-Kih2n!+Gr z*@qAN5!D2{HBPfQ;P;6|qnn3YvUJhtETS|*dI^V@rX(XvpFafkvo9NRyB;F2kE@s$ zKsFCiQ#@?r5E;oBCg^C(yqSmqLpRU#mFhZYzA3AGqYM`d1^V$gL0KG_x(mH=+&jeA z$X-c-XFwA9%Qo4C7k)=#73;w`BS0RDRlJSXKzNVWr;dR*682F(^t)#wpm2ceJ#6IQ z5;-%R#pE`kXOnC&lY$*_xKWOfK16#-5^p;`% zRJOQ|m`TU-y^+@TY0N!fjEvBb(!&4+mv@L9qx({S;MYscRi^EkBJ&^&yr(U33Abje6+`yhJ!< zjGE-Re%wZ;)RAu{lBV<+pGde9Vy#S#Jd{_;65a&+Sr5Ys$$jS9+r2`{_9S|g`kmAp zbPVe+nM-tqOK}tLF{@xT+uZ;P3=2urHVJip!qUssme~ zXo+Evv*{DV)`lkI`}}uFY$5PiNXYo?R~kPqEg5f9Rv#udrta0!(tDo^fJBdu4BhcR zwz$?kFwu@S2itP`y=f;!-9kD!ro9fUUgh__9k1jLD#0df|~ z{B^Cm&azb0N`tYEv*W*$?z-#CIaZfR&FaAFm{}g*+iD zAzDfL@$USL6oAi&(c6f;h>DQRA7QltM0rq9#*)3oZaeS(r6kK@7E_uj-?vOgYvOBL5~B`&gr0F^)>fks z5YAbp!vg)KREs%&{kARicUGqLvA98x9vzpl2pz1yvyo1sMfrLvfQwZx4eTVy5ps(y z5|%hq&n5Ayh{Gtc*5R%UmgaE2Hs6G9{2`T59LpugK*eOtp%b1S6Kh6+e-k>tD>3%7+Tas)7^vLkmn57McV zU<85LcRUg&5cgS+kVK4CAapq=n2jhuWWyzjK;0v?MYn4 zW7+lK+Iju_xh*Us)7E8HJ~nztQ_XKVi`#%}Lu${oj99%YU27Vb;*AtJ43|n!9F`Lp zIW>@&vR%MPDD#yo>}R=o^ba+<)RbaS4b*4Ug^CD3WshS_`=L)*n;a!LG# zG0%sH??A5~9N(zi=v7_9U7dL&zal;aHJ@Oi`&5u=Cv>G7YmM`PVcGVuL04h*IGF;t zs;Bt172y*rovuE?bwlvnkFN^rP3~&l2cZl{)&{m{<|DB-gqt(+&T4GM%qI;|tl7T5 z45lhi_7D5|%F*I$ZsTb?dH8e{gSji%=bJ|J+nMKmtCQsYnc5ITrCQ?-X>NT8w=fDo zVfMwFUr9F`^7kq_GuM(TeCU@8y{9dJtf|pvUQGss6mgRUu{D_u6jOt%-bHg9OWH!8 z$jQ9R0y)YOXybD)7$7R`3*Ot5q+=Ih>H@04?^dgulKb$N!%|c3@QU%9=WGu0dY7yJmAw_Nt_5PuQr%aF}#NQcw{^K19` zSESxf1KL%}-BGZ88S0`vl6roZOp~e1)|VDv(IwlZwWa>9l3g|S?@&LKt4sUblM|xu z+eaOrGi^8%WhD7f|8w;XCEdbma3ai_NEpm-l9t_kv^}yt%i=D44QU4dvWERDrGu3E5E*FtcKj)vMp}+Vix5RM`1DU@mjrx7GDERJcm>`588BxS<+{;GG-0C_XJjS*X zh-rj@F4GC7&XMZp$g`gIeK~jX%!~}bojLyqCk=z#6QF!)+LLQVa@9r3{|y4=n+!!e z(yw+n-A>1NPWSgrWSebTe96Z|72pw(xlFTKlrGDjZ^?@+0XJ$n zxb|AUq!{1f{1N>V8IM(0QakX>#v8{(u6lPh1<`GGjXYWHT-_(JlIP`g7JN6qv=k_C zk$&4!P;U>Z!YRxCxH9p31q0Zy5V(S+a_B@rZ>}{+MCa4Imc36wpebLSxlzYAl~?e^ zHVG$Se|(iBwC_iArpMfhf;Fk}imSp0eqw(EP`1i@@%{bPj!!O!7cI&L`Rk@qWL+TI z>-er|R=W9F0rr&jAMVhWHE z#ep0xd6uqaw>z+HXoP;)V5S}IxOGq(E#LP{laexHhI3;qLB$E9qD9*Pt~IIlFe~$A zS(vi9$7_Saa2MsXl8ka9dNq#|4t$|6^OMmFbUuq7#W&Yi$NREF2VkG9I5ED%Jy9R% zK-!w0*-jt!85Mb*5Nm))Sa?I)dbm1H8+xthT&-3VYwh@pVg&Dp^~$^e$({J3n}Q+1 zu)O}9S%Q#Qdml{=k;A)jr`)elV;=OYt^u09Xkk~vF?(2g{kcC5o{&LbceQs6({{e& zKfz2q$jt{xKIhk-J2%F0{|ZyH63KM?*jT3IPcqT_b_&w(l4}b{AnA+jfW~R&-PP6$ z_r2*&BR2jC-cmlyX3J+*+q@d9yc(yx8m!G*x#XjzdJ=&JuKF;fhIPogfdC)fQ9o-k zDSnp%s7qJd|A3tAshpU z(8XD2z@bZ>M|yn}obaEn5wm21tgK1`wm;fI2eL=IEmVI`3~Ybevz1wvl6ipVW+e#I z3Hv5YC|NFrYWWqA4)VD%-Z|YX8q6JikTE6L)|WbtQj*gF#$WntmEKhtCfR?dNRN3$ zZW-PxF2<8ei^_P|s{c`x9{Ev~%SVWS)w2DY9vYcv@ko#sC$~7F?XSIRE&>M|v0Qe> zFK61G9!cY-5`FnoYUu-XLQV7YO+MoIw$hg!s$%%H<9KY8a7D;CCH>;M95`rj{PVuq zX`lUCZXfil4-n%cQvG zs=sK9_1mF-)*8YlaBbfgoqH$NAQ6Tg((yr%rz%0uv1ESNgbyy~{=j;E_jWc}ciE2n z_{(+h1Aax^ocuZ3)ePEk;mZ6R@6xcjKxfkC)TyDm2k<}^(2E;Vk<%@)l(J6aWOu$c zaa>Q*$yF;(oY>8B$Lms`Idu7EY-iJrBQ9@WFj4m2iyvx=P_Ymqt^`-HG(|9M!39I8=D-mN5xXTiav)#SCqVC0UdjMZ&EIg^X)g77QL_ww+6UqjVGWm zeg>p$Q}Lf=+ck71>iZ#0N=T-i`ynXGNT%r-KmkkcIQs6Z)!9c&3ZU%{A&NOc{d1Cu z@i{|xRAbD&- zJMrG8hPSs?wiVoMLo!o0FlCTU63*ah@6L=>TI7#*k+)}b=x{quEMP_9xl;VqAO7== zUMw)indf4F&y9Ghc&iPv-j3&!7&@pzh~QF}L7$|)grihT<0J|b*#T0r z@~m`95#^RQ_6419{rt(&?FE)i{*3Lllle}_w+}lxZ_l$?+4F1a14{DaKN|ohv z>vOgTArJEz{Gc!T@Z}v!!PqAR@gBGG(%~KOv-`p%vrbb%@*a3%C9QJgYU{=Eteynh z&L=L?U|*i?H@Czintt#wy2_{ZSCQgke zXQqQ2xPu#-gGSjVPMIck2JEf+>`Qt0Yx($V<@k@{?5%R_tsmH1rP%j0fygHs$R}#Z zCtAp)%E&(0!MTjETEU`Wr#g`4d?AG?4EQb`;>LyWT`n`B z1AZlHSFKURt6X<|1mtuhh0pUpXzo6!yl;IzgPHBneFJQRI`HJc_<}SS=eJ^P~ z2GE!I7l6@vb@ea&_yP>d{gHp+(SfED;Nd}j2``z)ujewa0ASuB)E{&Z6||H9kK1T@ zMgEFo{tFpI1qmd=qcjfR>SFx0{E|+4t??@n9vy6k0FeFvbiHtJ!M8|&d6YkOz%QWa z@n0eE3wCPxpEM&ISeFJM`C7kL3X&332YNv~C+GmL`PU;lfXrXJ*&vsmXs7;e-2> z0K2c@{*%7^qaD1W2C#XRd})Kf4uPGl01K~J>p#iVKW^}Y;4#1vP5{wYR9MCDU+%wH zgaQ9@2k^ZH_J28`Um$n_u)h~T684WnFP%gIsw)Sef^Gu-751NC|In(_;K{+}KtSDJ zR2WPU2x$Ck5Ih|Q(0R2@vG8I$FAW|wH5`Blwv72Vw**l18rc8Oh69h411$b# zh(Uk6dg;(VwMoDqYXCv79MagA&U`V329j@o34A{b9uutD2!7pt1S6^N_ zlMNg`2&i~<_@6QeKL;KM>^%y2|El<(u>juq0;qXK-T#b@mog@Ja0>7*)Ek5NCDe`8 z7wdVs@JQg%S-`_tE0oN@9kp4=7U&=18tUu~{@?QGM<2%6S z?+<}Mf2PTw9%RXX8EQ>wl*S_(ku# z|Crza36;Ebu{AGb$?Meow{xJZl9$-DMIizI>o(~5A5MWdB*H7_HAp4X37{T)FH+E7#I9#BYSA?WaVlH_F4 z=v>_BFo@9NjWVfb=8Z&eiQtoeXz4yMR4*XDXOYycC@^8_Wr!uKZm!O5Q52Yqv$j0A zavl0@Ycva!* z!xBainn3W;V;WaS(S0jd2g{nnCMuF=m~u#m{2I#?z*?B%3te#=U5L`=T)_qBteJ zLya>>CHL(PZ1uwTc}(dCN5C!g?(%FIm_nA6Qw{nh>i*_(c}Byj$%`SK!#nNN zrcp4MWcl9AKWAR0}0%wHBcS1JFqE_WCi`g+7B##>{-qam~Smg}~fsMfOv< zyd{o6dt-ViVd>BV%TM=*}mfjy~>t@rQ+!NFZr-JOsTqvCUeM zlz;YUr+_C_*Z!^}oWi8T@+=dS@H*j#*>ciJ8znj{K9AIK*_49$?2RaLx@tv_1APUR zordH-dk{OEnFbEqq21OO@=iFx?d&0wDLd$ z&)ad~`I9T!wDF%<_rUGP(96~g*8Q)d!$f1$g+U6rsg^5q%LX<&*>+QO9NGLKJB=Z5 z!Y)A&I~|EA8N}Vc$MND}^_qBA=fvwWI8ivLsMoALpxy-bqfD{}8~SQ` zy)59f5SDrirt`fh(yxt_y6dK01=%XqNF)4|$O&p|;EM6qrvJiiHWE=Yb>ljrUA&n^2=FH2S#((NDjHR80wO6sU#DuK(!>av_t8Es{% z5pvR7k@8YmZs)0%4e7|>iPDyt5dy`LD{6hEPU}d&tTst+VPoCZl$c$&0KTmcs`p+| z7Vgy65***E*O9kWnp_r<>UEfN$c8xdu!bmlw{4ezQy1w(Y8VUB zHaZO0gj(YvCr1wVTlHfZyZwlGKl)Hd9-}*7&*2c!_(Jx=RFb)L!)I6<(%`dT-B`rSx#tto9935?Qc@$~iNg zhY%TmSl-d;+=Ej1pa&^Q##Ckxq+I>X<>)cjcT|%cun9T*n5kaWaD)Y8DPhMVm7XK#H>FU z5r!Mz#d;a>_1cKzlg((V^AZX&mCU^#j@kz{sa3&q=4sh7L6aiEl%i{C$?<}GHs$BE zX0QZ?6ElAjn#etqe4!e_wvJm+y_CWjY*+CZ5EL-UAz`12jj#=W-~U{9slR*VhveSu zvODlx&&gETafu4*Q&~O^)iu;-=8uTz9Ps-B1#@tA_FanCrykXV?$hym3b{>jVLDS_ z<*;0|40sL2XP%p;2U;&*L1!EKgp0XY@dxFNUi2X`XEFr(kGq_ufV3GX2h>pNa62)~ z$_cqD=UD<4+v=NPyTWFEBRH$-d-}eX-1i&t0xZmPCSObJR{}+u8x%g-HRDvHu86LR zM6~i+fACQ5p`E~X3xNxA`{fBmyQ(Dx)T_0W1+4D<@ZfI?E^ou)99@_7%R)V(%>kR@ zoD9IO4MwafD8tgPe~JE`-HLZ>711(1h^kjWg|%Ik&QFSqR?AO9r3P_E*&6=n@$!$^ z^WA!3<9I2}C~L62Kuzg(DNg2$I741wETtKYSnpf}1CC%Qq&wlDY*#b^9iIkpbaWqc z!@XNhJ(I~$LS+XX5oSOXJNKPRbi^=wz>aYmailnR$aS|(KaV)Ec+)Ho&_2F~1O!6c<~p zy`#3+Y*1O&gMYrG{B8{$s^zQ*+_vwzt_hWesli+^Ji-znp)2x6#TrU|mXbLaD;%?n zv=sa2Z5!N|!KqV($oJvE%y$r-cA=sE5LzVX&6ZX+1};&z&FAAdg6iK_tZ}8pRHXz; zrMa>~=sg0$JB8W358~T8YXkK>$kw+9)(%-PR&6DyD4rzOTNdDt;St$tJ#^FS&5!qG z{xH$&D9CX=!ZMR-ypX?Q*z03~1H05n6@5OmL186%VU59APpR?cnySq;dEI#{05Jo& z^VX>^CLA`kld_*t6G?zp*-QwXXdMx&nES3;NSmrPW5cILnsqn$^gQ5k63QFKT0Hh?`oqJ&^KpJE{FeAD5Tcoe`^t8# zfP`x$!hKrrIY8TE1LE1)u;%0XDvHfz<|>eKG8Xr#g&qdiJ=s%h-ue7-{B}*+WluU z+NUU;OIyL9?{D@#n=2dkUe4c>JO*a`9{CFVS@qdzD3gCppzYA&r+jqH+gsBxCYB<1 zBKMv2pzwMbtvC}^pk<_7G+0o#b6>L%{r9$J;hWobgWV0U&U~i0-mY5SCxx2b7!`n)vmSZN!C&ri8%$G9YB9nM@PoLnZf4v96H zRr&LGWubAPuCz*@^PnY4z1tNhPxjl- zEGiubk88p{ADgu@3O(5kU9J$t*!yBHP)OJ)NaakTY}W#fa4yuM@#gzp*{2f`B%+1~ z#~COxc%-?&R^U=VHfn%OIMT;H1s3yOO;Pc$yz&~x@@iWuHI&+UQP%X1kv5c?^qD=$ z82b#+p=9}diIs8d+~4WQmyswRq>Gupt5>fFrhzh5@~~CWL{Qgf4U5?h zw`;I-s0{Maq)Vf0P5UOH7x8JxQg(IhV)X?tJEK?eZGe{N{aGxf!wt)9g;x}J{!$j7 zLuOM>rv`xzG+iL`cl=!+$UlWxpe)&Gp@3NflGk=u>y~n%2md{zBxg%yNlZ zVcuy%Z7q%A+fp}mPl^vm<_OTjVDjHRz9L>9MR2)bncm^!;AWIE&AW~<^GV#1X<}Q^ zB_a`(00ODXY%UHNY0uv$S4gDg)vBSc$^mGVELVWlU*=gfDG77SlM@Q16Vxj+{G z>fZU~fG{oAx=o|!LkuDz>F+5~LC*T2jjhA@oHTvUnb9ql#%ICKmbW|mb~x8B_4)#| zc=$@n z&m9s0^jP+SH>f*9(!Cz5fbNjuKq3w7;m;tm#!<`w?1)wPrc^`}5$eZ8%{*m#=(!6u z`U^1r zf2Sw1vzST_F$X)2yg{){>&3FxH#swCPUIK+nrwW)c|Xzaz-Du3t!h(J6_RQ&YZrVQ zU9h;!<$_G2hQNfSprgrHEhWN)Z^0=0eu$%R>sMH+l0Z^Rg5EU8a+3oZ2R&b)o|*Qi zEMD&krxNaA9>m~tuOq|H3qVW@c4}jVn$iR1rB*s zbKB(lC>SZZwTy*^0+;=u5&WVL?@H^8c8)_xIlY7`hYy>R9BCsM)yLD&en2rppTqhD zX%C=;xjqw-w%T|;)0tPGg)4s;+476@h z1qc|>EhD3VFLh^mW-|&`b$wd=9_Mw z!BJqwU0T+Rb@93mGA2;kvJ0zbF3n?sCBJOT3#ZSbNl_KAeF6r<2sZT>6lxsoxP|9z zBl;BIPeBdIMRu2#;T5NMEiMKD53L@>fMV`?DSP*vd0E4n$wl8AE$eWR!-*1&O#(r%Z&R>fZbapn^h zew%eO-gUy|2iJD~VZ`_B_b<+(l(3=FGZbmn{-12!fZS4`A?oO-xO0i6+p6zmiKkyj z4ndGP0W?J+k8#dCM^ws1T#LKj!w=2H1C->A@SHN+UV#Gx~pvQ1y%RcMbR zNt<#={tgkpl5MGtkR^K}`sX$Xm0Hn~`|{u%6T<)A2Ei{V>&z=S^@1)*1E1B@7jd2i zzukh-M88Dq1V$MKwNm0^fl=rgq1~DCaJW?GLdxOq$$Yj6v`wRmEyebpP4VK zz; z>1xE~u{IS4PtrxOnwW0ZtBNe$4-BamSuU)6$s3G2G+DWa+KZx84{`|y+fUMWLNdv zvjEQNP|IdvsLjbDBjFgAoZcc*F<0B=M^UKbT$#MqCe4xV;z0b~(MFOjb|7yqb-cV^ z3UB7Pf)-b`n$Ngq?{l&%a1jqSIJ7m8f!V&Ul&@mE2Nd14fJLiwkneXL9$5?G`g0uo z2$*jx8??c*9jL=S)bm3zq%+XV>a)gE+G+|~>FCwOUjd%}Ah_z-!<~J5Ru#>*nHP<~ z{!~)e@T?Y}Xm=EoZ_v_5&odZM# z;aNIPF6$aIsQ#Ou9}~$INOvz29wI&tAtrQNq8_e3cEDf2V2cmm}MvoexqEz z`V}GGqMZpWtkGn9%eFhKH=iI67(+b3jdtwa(XGok&ByUm!`F^{K! zyLst)`LptlI;W^pQW%7wes1l8wmIZ1WEoz@wl#-j-w8=`g*(P7hN7`B$qUNYPaHnn zG5)kgyJr;=O$*yhK-B8uxcTP&61;SIV8@beTWuteznvcG!j8}VG`u9=(=j6qV}Vfk zrIwzT4)Uui} zla*Jh^F_U6HSjs4Trcg3-bzoxdkOjAU=3BDNxB8sLvVPm>5iJT1VQSMjUF<`o>q|Q z1m*|Ohw;d##!|7*;VjFV`TVGE2d3{PL~QGnyOY6SmxBhX3a&1N0fFk{lPlet;}fL^`?&pHso)4&6qko7vaj@Pmlp=cBl^P`KmR$B+a|}&771}rZP^10 z>KOJj1dbq?QD5+*nYASjS)SR&nyy_w_J%buj!aw>zTqTZSE{H(J^A)LUOX(mg=2+d z=+Rb(KBcsl-9x3oqO*sns>FfrVW58?s#iPCN#BdeRKz*~L$F`KuF&ih(dOSBs* z`66alAq7%*vBwpWp?}s1Pdncr7u$k<(09f&V!hGrkgRH0?$ z9P`mYzzCXRIw%)n*%FQyw*lp~Avr~X$2?ZCGCrini4WREY>iD)Z5Pn{D@teLqZt5P zddb!S1eDxRvLXxkVTRnifEZjwit+boSb}s;sC=D47@dKFtj5WM6ZK8~cMx&#dltJ24qb1=M9#BrlPpA9LVEbs5`NlJ%@{|LOjop#$a>qNC;E9yeCr9H! zIN=$y{ua^u+?3~3EAlKC9bJS*iEcD+ab%d`H}H;;FHwk9*%T@Zy(m6RCA0Tz~uaRVf(JK9z z{Xml~?q!&eEB<9)Z4RfBR<<55@R)x~nZ-Fw?GHyUb(43c!&I0Qw7=gU1_h%29_Xeb z3_uDPIH97n+L8OoKHB42G@w-kzVt&8WG%#$w)rS99{6DY~or^GK*hq5$V3go?B? z8++&HHXRO16_Vv>YPzvVA4K%1n~^VV9whsq+oEp31|{~du1`)38=cq-=Yrgu7M=4} zCJC4qghFez==7VEL|xyr7w-|DzwkVeI=iHN2E?SiveG~D)jvbCIun|<2I9!j;ap<) zB~HA2d`8SA@AXRoFg|`h?~Dc=9@(FF^j`w+1-k(SH$?Vt5u0_p0rXk-K~B5+XnlqQ zOwa0WJB~Xf8yM0T8Mu7~JTU;R`)BrupJZ?skO_ltdRNLxODxu z`^ZgdLp&kkA}twp*x`=A+BL)1Q;_q`=I|&p7igB7q{`)OOL!dSGLGYwA(u%hiq!pD zS~8|2I5W!~+G0NMV6UB)^Ko{Dgdg~OIhe+$2H>ivnx|s;Y0mTe?hF6(#CCi=W~kqv z*v>ys4C#M?YJh+0vi_tndy~K|{>_^H&vOK+W`g_tZJa3p|NQS}z5*!Eji9)=^8Oc2TMX}}yIl@x;{{B8ae?GJOn4$qN{2^$d^ znFCF#97vOQAPv@Nc_M-aMSnJ)xttDiN|$=>=j3<>X^k6&Yyq6Iu3g2X$SBVtr)o3EkleZujHE|DO3$>UUJaxmApC@SYH(No-li2?tZJ z4|{wBD5t)?(zJkw|$>dyy1;5-kt@tK0*uz)8me-o&2jaFamAx^$r<*9GMz1JC-GOTp zp%M3wYL$^u0>x6cv7xC7(p!u;c}lo1uP2x*V|O+%jBWG--_RQ+F?knhHiXEBWCn4| zamlevi%g=`qD4Q3?ghWi{z00|xMFI31pn|sAL76MRlKW4$bcl@z@h(nk>G)GRj?$$ z&SG#ps50%bj#=PZF*q)mDDbuzTpX~vjc3(kgb8=b%@Q28w+8_o%4(wI(IbKxH#{av zSg7JMEP|&^%gdl1cX*!{ivl+Q{hPqY&8h5nLZCl6c!43w#%`)*hu$)O8vW#osMIPA zs*Bt=SzV)RkqyRTYJJZJt5U@Y-n|hHC`wS)#)(%DZ|%;wAPMTZ%V_K(<1}F5Rrt`8 z0CU=B4Sby&k;mNk&CYtQhF~}bhgO??Yu|M+=vyTDLx$3IN!G`}b9_GLrJU^FYjQr* zjO`EQk-80Gw8JUo%ewqY-j6=unJfIURUTY+Q{&j3Vfv+~vU{|v=~)FXhry~BC6LkO zdd@rf87y?^%5PsBq70aHsgVJ2kb<5Wr>!U64iOJHXK+VlJFB6k=HY_i_zvkovunE4 zKF1Qwjmz9XP^n{s`OpNL4(d&HkNWOX%2M4#&%hT;;wol}Yq5hheXv_d0z>bBxny=iH z-+8zt4u|%Z(XX>i?%F9Vnm}0DRdN-6%$(_ZlD2}1DvP&$`Xu@E3Nbf_l9t!JA#r$u zwlB?vev%e+x)&+`$f9`F>=JC@mn+(?m$wnluRF5p9}BrWlxbvnQonYY72)pnV?alX zZ%FV(06qVdoq1%h_2OO(p z>yoJClCYsT+M>;Gqq_l%C$5%bA?D9@cxNtD-0LoG%cZibm2n@cm#SeYn|WpQ5ET94 zzuLjtPHxD3j>*tAUpQbNP-1ty4St-*8(npYdB&-#8Pz3g>8u4PmJk(X>g4se0#9lK zZ5h7YT1jBEs$njU^Jhxy6xtCw?B05~_zNIhIJhy1x^1|k1v=8cP}IT=hj`&Iwm7A< z%#d4^HzkN6Lne z%<3GQ@ubbDrpo|WFziQgvs1B~3Q}imLIgg6$WpQCm`FR=Q==Kh06O$$ZWUU_inLJ0 za+S5lR1=aCMN(?kRVv}syK=^MlF1+#SqcogVJ)f#K9uz-aRAzk9m_}j5gS=o3VgXC z>4t4Hlb?Mq524z44|kDU>vn_wKiQHur@o$Vomf-1Y=Z&h+K11`=3^k1VhWdnb`N`Z z4+jsHvl_inH${aFdOsK(RLZw(Ap|(vGKO!}wu*DMp#cV@RKTPj1-TO7*)~pElahzZ zy8A3tNQVMa^f3Cyx3F4T;P|e+(vchrK!jdso=K5xr3W(#qed2%Gl2>%w>(0jzB1|K z=QJClbd!MRwhILnIJ@dz`U!$o6h9uw4HF6r1{IT1;}EubwV@^xjh|WJS0%YvU}H^9 zuJ=ImmYl&%M#F&>1tJMu#?QK6>)E@$aAvDTj9$T{P<-r;{v^B0cJnFRKMuQ)0~UuZ zq6lp*gD;FwR(v&sFD_=&Jg({jh?WF%jSxN@@~LNsKdB z<51vSNde=U(XegOejR2i*$>K)UvWqqVxKQHV&+9_NU!xV)ejn*sZ4}DzlPGq9DBpu zmyV%Rx;2XgQNcu5!5ETwh(;)q+-`^`-vv=o=|t8KkNCUh1)M;X;tpBP(5HJ$rrnRv zfz*zGEq1c-DLBHp9WE|K@S_vE^OxwR7?PN|Ig-GY0WJfx;p;i4+W^cyw+@y zwMiW}*73z@C#=v|eX*ii5>YY(N+NtWx2Q*eboM9$IGWmhHQNOe$JZ_?lC=v(4z-AF z{1g3sZYK;)ozIl0$z89LAy0SKTThdQt$}3btU^Zkmx4q3xGjAj*Jt5gFf-9;uCJ`On`JgicjB-!|;5QF&pFia8b?FYE(E6u5olE}=i0yKwf|x1EBT!Ovv?p0PoFNY9a(IB@MMbLzLn#kr8T3KUAqcm0g8rrIOnDEIF<$TX zrdi0$r_YMgxQ1T{2api7eyx*v=tIKfqBnNoHXgbErTMEc@a>&21OrRx(X#@C`S3v= z>HmInLGL&iqwp%-?gk8t>Ax^t|FuFveZ8={GSfu8dx|W|2J?rz&`+; zR|VJEB5Qa!N)4I3NWEDR-5RW7vV86WMq2xF$R92l1hYZ13i68eTkg2TKQ2Fi|2X3; z<#jO#!<5R`ci2Al3iAfB8@{=6fNHYp44rwTV_Vy~c=mM1JMs(gc18aM;+`hhGQn09 zW-^k8ddD5jG2Dy&OFAkz4>N)|V8HIf1FB8}LLRDErWJee?J$(Hs2giAua=2E=4EQQ z%7;BvQ~JanY-Oy=mhg;lZ?xEOC^R6rm$bO<AQA?$C{y4*he+6_0we zI|J<|xH3>=d2zc!Z5pR;e;{?HGy&4_{NX=Gb*`?}}9tdl%X?v-8cSvAy;zeW7 zC3aTEf-Swq8X9>C%AtxF&`h^K5^`0geVwtwVsj8P+oarRcE-yYg*KVRspy*e7RA7N zg!=#*LiFT8uOwCC$ZoVL5+A8~<5Xj3H#4tkS%#`q!)>JZSZ0&M%dy;iV>j<*aLHvj zu)pquqIXQk3LEFF;!)33rZKZkLTFT&7YS4mzIt3J{Neht( zp?X)#Y!I{rhq>P#`A~30i>D!9Z?6*%38BGAo(|=(wAD-ZcTq#jvoVFv1Z}X~6=?Ri zd>6EkqinQFo^9R%h^3byG44-Pm@Z=>=@JpkEizcv*zsv}8F!oR<$Vv)ZAt3UG^?sQHe$^P=U!G5$0?h9qH#N__U49(H3(Lr>M5A&UFaO z{kr8~E}exD@K-wGvJ%7IyZ)jfi8^r;kf{@z8p)hbo1*oVx!D}fht65J39`E~<5p;W z@gVL2W3iqZaEoCzcw;C3d+PRxRq#@wxVcn!AGiI6bb+GE$p zUjhm^4qBG)arVD1(spv!m&C+!ll+sqTK87Cs1n3qBz zs&y+|OqZcoSv8epwWumUM`%IcO!{egQP~IMLfEWr|AWE8PZSxUFK^5o)+ne4udE(b z)0UE-&2KQ$QZqR+exyx|dh0Y}d!YPS<>|6;vNCsanAif_*@6c28n_?V+=Hw-!xq-q z`noA=0HkGn5hn+~!W2B7(6tApxV>Aosg3D-!6(_*i~(NMX*#P--f6!NhO2ZhtTrV%RX0!Hhmv@WkhBeQQK zvn%N>PFVO6Q!rO|4ar4bU%H4peX1zgW?T&-PeP6>8Vuv>n9=$)D^A%Ra(tH0+%ZA) z0Fw+NG{R(C{>f6uTh6;`io+GWMXg=&c8$6&w)=L@pog=4sN8+)?dZoy~V?4_U||;p^HDXEMLL?`8drFOLX(R6LWy? zQiT7F!~0jM{=Ysv7=Ua|TPOVakAk+!!7%0C6n~bGq4~Tvtbs(ZLxJba)R_f&@hn3MqbcBc1M1k}Y)T`UqilWETRmaE^YbEouTRi*UUpd^(F_LYQX~cu+UBehQ4fv zHOdV`jJWpoR_Ur%O}BH`-GIx0o*0dl#`VQu=v-fH!@yVQb8 z-o*!-uFapuc$0R&73ev~!=H0w%jSn~_MWhkJ@#$~kn91EU)GY3rZ4J3uf2zb$h(cP zA#K!~KlJuTSQN(b(>kfCBWR85ZHJ#4lp3AeeKKAm15(=4*VcbhL^}31C8R!}uwa~T zT;B65jz%=3q#~}VO2C(QR$zUY<4tGxocmOsBky6>4()rTE!cbX%03<;QT$jHuXZiv z^W|s5)r%BBqxV@)N3l5i)UNfJR&dqxI4>1no;$O%6OxpYUFooOE_#|k28q3o5K$Yb zK{lj1ervMfqmX`-o-+g8py8rKf=+#IiIqz)&2=0-F6>`(+i$NiB$w$U*BheFXnDeI zeaM|kOVmhKgAppEwv7gLFv(+0 zD*6Pt`O#*#X_TR?&y~yqEioX(@ZDR;5A48p>7c*RYdq{zeJI^lKbF^8n8M{fbWUFA z#-iiRahAt>GHmSY}3iyn3F1RLh>1&tFGfb>M(H$y7$sd)wz zvEXWlR6mcfU*R`oEG$|@LxP#&s+N`vry~{Nd3=spbuXKt~G5L;K$UGMdha6@3`m01Gx zd`Z}zz<;V@&q`hAkYDE8m(i9NDYNN6s78j1;9uv^xEM43*sUVBg~MSv#+)_@iW9y< zkRVb@?HvL1OdcxJG-JT99N@~e5QOZ;SWVC?*-~fp9lIV@x&I*SNwLvGD%(a&UW8V= z6ZBO#cRLnGCLHQ0^I2;j+&=Vznp6e^7jA#a5I8pv`&7u>e}ozrp}CD0!ml%SiSw-- zg43@?nm1Ru?9^K+%Db#z><9TUNvbIp%KDf2=f!ZIg&nb`T@$@A17TloBSPr9K#eVU z>ctxI=(`Ho2qBFR4U{BGW}m(&iES>LP%iQw5^I6mN3Qa5a9C+oZ-Z|^f=BKEa)l#| zA}+8Fy3ko3)rzP~VGqM4v>>+=gYfr6{X(Dy&$OFcF6nBpT^%AqGYI$xyUtYa&I<(1 z2U;9k&c-N;KR&>Z)myATQb$JWGIVm2T9B)?^**xDd&jJRMn|Y6a<$v zj+~8+tuKwtt;j-6UZduaQ;_)pZmtxnvR@?#cc+JnRyk9SjjNnEA`}Yo)Tnn9q}Q;V zpt8udFx4Px+FG%Zt!n$$n-(Q?{85SMA(tt%aqvjX4MKH35|!#I#0R=m=WVrbe}u%7 z^~(!GlH0yAWKO`%<9;kr?m~JhJonncy zBK%mmqt{?2_&v)^h0?f^oGq%=w9!nNo~(6zU%w<=F*F$UKG_tX$+9w-KgUux+>aq> zj6Y;>T1G%pSwEEa zZ((Xje2yleXi%YNORkDy6%K9}FWFx=O>39TA}J%1TuvanIZK;5h>`C>rK`)d?1nqo z!mgO87I--eHb;rNL2mG7j&P+W?TQlFm^eJ&*7qg;)dXX-C8{F~5Qz#i=c^%2&%nJ+ zVS`(>OVbArA{+m`OV{G{Y1fBJEcv%hswciK^5aI2E8MC=Lzg&}k_d5dW#5@lApFcj-jj;DY!K-5l;<+&QU|tALQxT(Bdr;Hm*_`ZVC@@*8ArxF~1kQ|k%94u< z&`oWvt7yL=1q$E`0HT^A2wp9#wS3zQTNda7?ms&uHpOoMU7EYs6DG)djV?;4bKlc~ zzNZIK(|A68yiO{*5sh3@9oVu)e?&#&mrwT!O^d5j$}#7XN(=0l@Nd$BUYxtKc@@xW zPE0Q|Wm<_iFmK?1m1BhnJ4NKEi*7T6-eLImIq?uk7RWq;2&giBZ8L|i*;%uQ{bt5g zk*}Vt5(w;L#A+2SGI^vf@~SC##;KR}R(5A=hC1^3-qog4&LR0TWOS)2R||vh+}?BP z7%g%nN~XEFFfrak=F-a%Z)Ayqq>$~`)b*07+N|TH&pp@C3t7Z9VMX63$UF1V$4_cq zmpf+|#;|jA(*PaFQY_sfct|)!QK-JGRfeN)ausFAuGztgq5QJxf%k2VLC*;5@GYL2 zL|NZ5#9=H{V}NdZ9?BE3t?XMA&3$Gujkwi~OcaIg0Saf>ef}}h!b47m)KOH!x!m3v zzf>AFcIvyO9Xcf$AG;7i5v3IkmC6+P?YIt}mnw&;WB?s8Vw{m4!kZ=)=o)N@Tvv2+ zkFA2MF8Nt3I!9uh2=Y#+wo&?IJ!7LBhCy&L`BDj)pP~9XMr^n1c_F^rj%#Td)nHuT(mJmmbqU_*8#E*#pETFvN2>f!Zg;}Rn7>d5h`grDyrks90}3tthuaT+ z{ip=gMZd}3yh@rsaSLO=iZclBRNt{SUt_F&O83E3-BIMKIDW!a&yv($l8AdjKcVb+ zSs0bm`r_Z9FaR^3mR-+g(V4!1^;G!77aW0~<5Wqg7rpq$3mjc?gKY96^)`k|*@dZ; zUs3TZ%s>7#?22j}48n&G@&ARR{-^3y7#LRriv(gC2mkeNjWX!HG4^lu?Ry39p9Bm# z@Vo{V5p=x@&H(mb7-|AwJsbovP;vb~+e7c^LJ5GiFUBHPz;`@r3U50Mks>)3TQFTV zkGUrJ=3dNYGMWTNO^>W42U9cac$g-hD_BH(IBOE!ISLUjnLIe_Pnji1s3zi8iA=QC zb7!W)x6C)bPmr^}Ctf*5lLzv8zsZ=CcUBm4YdxIwAnA8Z0-Qvg!HYy(1MGf!cms7O zv0hoIT3IJ|DVX+x?a3SmIC>be2)z>8l(AyKtj=5@t6-*0q;+9P(@e@-qf`_mWjUpb zEF-h=uKc{&TDawolmW3VT?J>e?&jLjQkA89q59YAnl8<+Yf|5WGs1J``4G}c88&vZYQi?c3zfz%`eU5+pm`- znnhx(_b7pfO4m#636%s-sf;wP^Z2yHa#C|Q68W_i4b6#2E&B%|qw!BUTysBKWBCQl z4kTS%&beoDzpn88?0$83tL}Vv+wy1&KmkKWF#3%2gxwExH2d0AY;98^`R{PkpMN#d z3K?*5rrgD?$&|L2;x&1fpkWj9TXioj?ATJS&00g-5y8SctAo%KPf4TUYPP<^A8U8Q zZzx?*omA>7n(s=HPPwv!QehDqY$pdOC-AguW`?5u1dQEgBJ(l?Eb~yoPGYBtO4cb4K|Zs%R9($0`PwLF%VKPjSHu=mrm+i1!8kj__6@%p zFS0T*j!c4FJP+U>f?M=)%6HN?0P1*>;c6U4g^+KIO(IxA_v!{1jPR3XUI@a<7phpo z{n}8%nwJP2)jOgB6dVEN1<1E{^{)N8^BQ#g#&i610U?I;UYdivU|vKx6l+4CzOtyR zcZ5aJZG3P23Wo)B($4eC+kp=#+!Dtk-ZsVR9{i>DY(VoW${^fJtgeUE4=BA)ip*YO zG`{VyL)a3oyhj8mUuv>G;lDB&Kj{6A2@cgg`Ym6V`I&^TDYGisb3?^|eOK0Uaw&)2 zEUB2vK%v}T>(l8+Ml&kf>Wus*L11Y@lG*}e*=57Jf<3FMUc)0^ZN7IKcvRs@pq3R& zE8_yTJqjz&^&Of=J#BW_JRo)0Jf<~=u+z|#)AyKcuH3%-7f^}clE09jc7la|Jrcva zOjk=a;u-K_{pj8jAnMe)j}TSa=M0?MIUl-L_vqRY4Ruk}w!+iX^ zF6J7qz}%dvU5;*G=jm$)I~*+r8;$Yc`2E23S3G*rZRVd z{zP-S+}9U@TLSx2F!X7)Xg}n?UEpjwxm~4nd!z>Oaga0l1gU>e@9oN&Hu5~&h>d?R zX?NgyqH@Hbh{cpExMGemZ%Ls=dfY7$&TrW{8EH^rz>Yy`ku=%r9sD!w0mLA{f$_wlHh7uD4i|YM*i~{Zcmli2DJHWu$dF;goJzxrTUQ zFM4o&#tzo3Am&E3$koBq6WEIf!7P5bI=D@dMmYK{So+Sv7X=}~1t+YoPy{EvP?bOo zt!?74YjnK{k4?#8gD0M~Y#gO!hTOqWdZnjCRVn75Ncu-;1~)TtuAu&WYR51Ie${?NWEs^}alzEHhJNG5##{M(G-mcdI#f^I69U|J(bezrc z57RG=bYu>cdoL7O$!zv57pt*R_sB$4FAg11qLXx1bF?{&ies7>^polGO}n@|;|lb- z;$H#mFjGEwvXhuPe(|&mQvyp0#2(K3J4gK1&ce<}B{?!Da_s3`z3xa+Scun~cTh<~ z)gqwO#9Qgf8q*kwdWp}nIuFE4Z(~WnSfdokzUp>#Vf#)ATbBlH>T2KGg6{2Ns)Z8J zn#ZP$BDxpIGr?R4TUg27YV=>FSnq#)f<6ThJ;~ah)px7HtCJ%^8Egeg9}Tgpco8&v zC2&5lRbFDXOedWx*Um&Q9xYfWU1^L#Cds9-i!x_&elkIkEhACsK@5-a@=Q05k0{8H zSp95LL24+?`C@qdOu#uy;h8N1jI>VCW@O-94ZFYcEfa8Dn4HK!_xad3+3<(^jSow8 zE|Yu)`{Bbb`hRU>zB@=5K$7}* z>_yxqc*)<~+@BQOKUah7U%(On;yT{-GNk{S7;AvV2ENwA!h)tCARPY!F@Ax+r|tfX z@Tc3M;TZP2jqlisZ_q+(-#F-Gt zi88kC;UQKrQ0TSjTYipwgNx*#X36BaG4}5;6BF3++Eu3BxhLw`@10O?rreoDla!vn zQ{j4}MCn<#ThO$twm!sk7kQ8m~YeIsTfce4@V9fr!mQZTz`p% z*JFEKW@LWcjlV8Te|_TpI0Ogo9Fx+Q3cV=e5%+u6r-j9zb6!!|zgJzz1Nwz72pk%LCK?xc%)P8g-lzHaZJ9oSH9b0_waHLtFgj+dXRu2BEW5BQNghXg0>48(U|2( z#9Ds*PfO&0tS5n{(&S=43}qE2>^obE7#x(GX_<_%oi~by%+pqs-4h)L_O8HE8K`AT zp@}yxv=RQYIWQH{Q)8yXdF?9;i%70)N@DZx%hF73YonOS#NX}GLi|Rg0^72`TU@yD zmiS02yM<5f1_P`|;{6{0N}Zd|DZEm8<^#XBw|ho_d;Y0{Ht89`JX)oDuyWEX+5_}n zqlDDk+wguU<~tox7yM=xb+2UtmxzsY0pn~sU}3())uEGCf3XlSb74-;;)t2hyD3Gm%cCRwpt1U zF?=9I04P7vkMwlOI1gJt6sAgnhPZH@Y7;&MxH`6RgED;R=~m0QGmcykF-ca$4<3l+wr_4Ja_Ze-DN-Vq8*r*usz<`?&wT(jMqTYT7;`PSy3PFkHX@>@$9 zLq$uvq&3@~?^-*ZFoeg~{8=0KK|r3VeXeUQV(|{jq_Z7Xh$Imth6i0$@_86O+HeM< z2>@b=YmuV>Q09C+?GQ(FxRgrBWQPl4E=ij@!PI7vZeiuqNX#8YgY&_@sLr6Z%{KNtnRtm937hp|0!{NSNOa_kU_9ge-CBU)~U@>lue5BLIdIn?VdJWpAt zU&J|O&TqJ=Rd+o&WoQ8c>}j9*7VoYG(t*>y5afWmDcOUCUy6J$0_!5X3q;h3sd(XL z9@13q(Qf8hfiiqaQSllhIWn=KNIbMU^GA_oeI2KS(2<+{GGK_?9z?E|A3V1U!PX|? z7V|m3_}-rz1YEK{i+>Lk83*IhNXsH3&}?tH-VDU0?N_T@isr@Ya3wsarykX zzu)wLhiY&nA+UEoxY(_2q7-8>2;GBuJ*5t8uMHQAMM?IzcUi%~@#Sw$ms=&~6!sWE z84x3r3+%kw2YHB{%^a>dpbeb=aH%UI6&#!ocFYQVs~=u3AMC2@b0-)dT4kkFf~Fw+ zqO-g&*!dmVljYAGUkw2&Ik-W_9#k5jn=e4D9Pqk zZhK|y_eJ-CPk0aYPe{sD6e#KU0OHe^zzTytf8nKR66p0Ux+_T+z5Ybd=L--NaZ+#4L%ct0rH_gSNPS+ znnI2W-pXYuQV)AMSCL-m!OT_Ysh6U1C_M^T2nuRbJg8uXh4gCk#k;Hw^UkbOm5U+c z;4`Q652WW_=_f31d2o7t&B34vaK@!{gY!A=i|Zr4;b@iK!oP}F4hJT$E#MF@n@y36Xw!(DvN6~=1!vTOCyJ$HG{YhDQ^Zz*#R=l{i2S*f!o<| z!;tXum9J5u>Gh#R+Hy(d|=fuJAPsOqz56}g2?tNx)K*V;R8xH~)w=f24Vz664} zCC3j0^J53Hjevpvd+@FZ=AHJNXNYI^fKX~g2t~XHTrP00p*rV2nqJw$uQl#o8frq( zM@2po75zb6m@$hZf`Fn&i}EDWil&pJ$xk)@@|$?<*1z;={fl0L#Sa~#c!GcJxB;m> zGY4*&OlezYvomuxd$`K8c>?6-7ef8STE0|N#w_d|Yr&no!(vFU-a4XaFda8VUO~-x zeW5d2BEKpo`#%E_SZ+D7&{wD@1A824@kn9)h8q-~R`oZu+5xLZ5uO`c1lmg(eG?lV%Imlg53e6A-qFu93PjeqE{(|Adb0nVT(VbI3y5k}Rg~ zf+ku%lou9$?KR&guWT^`>i(0e*gB0s7xdC7>n7*e zKD6D)6Ep=z(rtspAFtz=9?k-}Lr+!VE(l>skgyun!2tSMCqp#t9R`Fs3>;D@TfMqh zgt6&CC7e&ArM!3UiI+NXopR!D3i&w+Oa6FI9uY61G5!ghpi5P>Y%cXvz6_LE$z6@ zv7Mf<2*Pq0(X~m{O9xhD?P+n;^gKQczHqhQawXhlO+T8>+{LTScvE_4)eF-vc}5xj zjsn&OL9j|7vDj3GuJEc{YiP}<*>WoLPr)nWE299lvwuv2YDzr<=cl}9iKx$)X|DYA zuR6kR)4xBC8~Kk)=4xl`D!We+Q;Zk@DS{#V!193k!4RZyu!N~Ax8dPkiomg8h)OUU zpl1k#92gMT_s=h4AW|p<9#|oWCKSR64C3;J1DF{GVTZ>AO1El9{_vp#@Bcc#p!cwA z5%pi+@_$@dmUM{Nzl@G|-;x9P{x>w}xe&tlZxHn-=KAB;)|NoX{TpNf|NrekP{(@| zCt3jk_SZMgeR_{04ZGe&pH8)Z4{>^jw*K1$YO96#_^%(E6dn1e4GkC-2lLkpe2KD0ZODx}8OGz$x+jm1#%_yAKDFmwM%c8m#8-vsukY4fv(8T;aZ#?Q^~&|7B7>}V-B0v3w-4b z9))@YO3bKpMg{o{_GqFHsxfNm7z6+gH{;7_%IS3T3f>S&=Vuqut?99Wg5>vaaz(Vv zjc|9d63Qtaz5YSHl$5T42XOL7TlL7fxm7hIl%}@M&uNvpM|W)yjoX2o zm3XdWen`Wrej%$T@HW*h3*IayH5c>zg7cFapq_kDd!vw5=9!yFA46Bsq^ZT9&RA8} zVLM+fEZf_OD&X+|dOI^ZESmmY$!!oUXD(dK&MlAn)qR0mh!HN;&BZNMVd}DdoInK3Qk3&gHcc*rM>97;68tQ=a>R6#O+9V3kDw<)TN+zPVWYLSD60 zEPA!W2oaX$Ud&~8H5RTiizp&GO^NGg&LmzIdYq{5^c-8<918aYuJ%`&LHN%VPTrZ| z^mRWHf>8R%BG5w`wr5G@nOq)TCaDIp7|bH1(6?Z3($l==-L<^b9B_45>pk zTJZUti zwQ=#0)5%=P(BBa8_U){s+`9Ar{Id`nqd9R?VoJF=B0#bmn=Zh#ptx^hbM}|~jc;3E z5)A?2Y>mp^(@~;TM14_{ox?-N1A-kwZn>7)H?NV#PiN{=>Wc!uyo{Z;EPSN)w?sA% zdjoY%a{;*AuOn~^Ri{~N9nW9;B9bDhgw+MikDHEyAT(iM_+Vh#pkZo|khiq_1HLuZ zboI_JHT88~VJS8)E?(JG3QxBjH{Z?M_R$%JjdE}J*$$0sHy-@xx}O~zlRDVPkq7vU z>qMg<>k$(iYNXSnRrpfqd|$l0eYWokbo`ll&kuOkeikVeGTzUB8=HIigHcy7?^BCIHfx=(?8{F?{#R@#FJX!L&<67aU@dd3u{5>_kBPLU?<@DwF|np-;yqU;qgDWYVh<)y@G`SEjIE=W*| za8StP`x2Z-Vzx=?^2deV$@-{M^b-r;Wg$RIE};?3GO0%zp)qkop|P%h1@97_cUCn= zKHw%zS|hBj2mSL4AnpYw(!+UOPE(MpX2Ax7kY;65;{avex!@M|f-Xc8m8xoO?tNQ&1mR&}hZ&o>bouWETBh%_9+RN{f|CA0N`0ZhFsxUaGhP zxYI6MK5|w9+4wC}ilAuZn_QDqS(w!Rc1q&R=TuDlv^gZ__`{Nc4CIRO`RB5S^l!^L zd&TkF$dNcs%CL9JiR8LGRixUAkbGb8l_Ev^MwuMs1t-lv72sv~zM>h7`7c&j68kJP%*_!NKmk%tNRIy&k#ZM7$><}(y> zK{a1_tV70im=*6R<&5B;yiD77s%Gqw!NRYfmQ6Z7sWzeT{-ZL1U2Z{*V#&o)wajVU zI2AgsFsyM?u?*K&rJgo{ZqXRBtlW2cxcvhpU6zZg@#drTk}GyxYe}D8@yIInqLu#& zmmiR&luP!bv;v@9T&`SPscc@RZ2lE51uc(tR7NSu1T01!U352@V%m%HqnfgbC`K}m zQX@JFpO~_UG#y+X_8?+L8LmhY=0^_g2d#snu1 z?isH8@(KnPg(`yyE-OZ&4LblI*jo2x8!XU^G!HfqlGF$r9Ga9Q4C*HgWI@OmW!M2& zu%g8;r=xB8N1YsxqrY7`UISu_*PLQ6`5AwFUogtVzpUZ;0ZmsJ*_>gLU80q%VONJc zI}6OstKA9)NOIGzn$UbrFTsAtbgZm{N zd8Gh}^@Op_q*IbB&YEVydQc<^w}&J9!!2nZP9C3cq3Bm?IhQ9?O9w-tfsFIUN0#;u z9=_V7A)NxrYeX(4Nlx4G#^FN;PV6(OGr_DmZ2x*>02$%$f*GV-9=XD+0oB#%&%5Kh zaS7<5iXLlE?DxS>gbu_tbip~4a~=dC{4}v1JNIiLjZWNq(?kRbPw`PNJVPj3_X5F4 z{Ay%-UEuKCEN<7uhH?WW)G>SK65ON( zH;_zT`aA1zUwP(zXiJceFPT0)G@0E(spuHo) z9E0{zOq}MU^n=})muaXtIgfW>a1dA`=!$1l)y1=s?8N=t6H*p6RMDZn z_c`*F%bb^E->zNUcCwz|ZC|fl+;OszA93HWU7QPNshvhuZ;!%VbVp2oEO<<+!Dn-| zz#-=M+aLK;2z~1j&wD9BJJ$aVmVkdwQg{z!aDeaNDj3k}G{o({Ax_eG6)EtYO%Dxx zTKF5`0N?2pf7@KU|DnJyLdgA1AiT#^e|sjo-;aCvwF=?%H(~Ie==-+^6uAe%@!!%Z zBH;D{1RhY~0RjeivHy1@(0h=C_BXlkC!;6Q3(E@fJcOY9oBnza*BJgit>Papqw2pw z)O+CfXDED-=>r5m)PJe8r2qd=SU{ux_w?FZA1n;;;T2-(&;6kJHwf&%_TTe_$bapF zp+buNYyTbFK?`(3g~R}OsqLi)yrsMDkMB{WQxL$)w}yY|M@1LGo%z6){{P5&$KXz) zZhbqGiIa)ViEY~x+qUgr*qIm;+jb_ljfriW6Jw%(o^zghKfLElSM9yKy1Tl%tM*#! zUe^sF84M8*7S%#7>MRLG!FpnvFSImfoox5}_kyzOF^QPEYl_zwga%#L<|=o+iuTp2 zX8Xt8s^1#|m-aauzLoIiTOWc6LI~E6DDSrhT^}D+e;<7V8GT;^UBIe_Ui`*~nt^ZU z*ZskNpOA<2x%s|_Lcf7vUS2aVQu;l}M~TK(z6f&c7`5_Ig^I?5gb-s!=K7tV&^)h7 z@uQ!vunCd26|OCuJ?DEK_a9-uD*R)HRd~K)i~i(H?cD^Ut=><4@#6~==FUv)r+(gk ze0%yL-|j$!i+i*Ci^#%hvZo#@8x5e+MSIyj)a27|ZX#E&ZCa6z_5uJ)HzM(SYjW*JJ6Z9Db+VzPyZJ(ffb{%#9nEuC9ZY4VG3MeQrX*w zvp3GO&quEpq7%4#7x&x$S)Vn1d?<1TM;n$@noE&&Th^kM^)6=2s;}>yXAso0E8%s~ z1J5QOb!BOm?IFnS6c&oSRTJpuNo&IU*Veqel01-{NS`#`K&gy}x>Bfn#M!8;5$`am zyODkJiPSgcQE^|WA~4lUk^-DfjaO`q zEgB1y4H(kOwG)oZviToE3i!7knmx$tqI8-cRvUsP%poU~!I;xvDF=idUwcLH1NUv+YDX>V$(tC|2viq0C-+$Qc)$mJZ=v1Qu zp(}!B)Mg>hY`(x>x!Hlz_Z{NTeKw=F?C}) zGZC&@M;cduJ5<2mcMq^|bY85VkDGeT`m9-YWyjs>n;CR2IfXK$xaLU&^HAF3rkf~) zxODQ?iW0VQpqy%ZDAP=s)=c+soEY<5dbM2KrD82>KFQ3mEb?(Hn?4+BJY>BRgD`WM0tisy6Ud+wDM{e+t>N)eel3U;lFaxErNcbAq*$H%(5! z>qJ_YZF?t487eSYdagumGCrUjYui^s#^c$sGSJvEDaMYOt{G7JvzzH!l+S@AkPxle zFf_b7QPm@FYXBZJGZ1s7L7^WH9a-Bm;KP01ry=VjHT%Y{&v9+iVZ5z|1_XAqU!{jH zU8A814sIzWD}~O#Vtqt%#r2)FHouh4kBwt&@f!N)*0`$|I zt0@ZSW!r5%w2uJ5tTUAp=v{!v60g?sxmd?pQd64@Ul_3Y)?u-W^;Fc8)Gf+_nd1TaM4>{BQFRa3ERb*AvYzO^+Pb50s+N{C z!$j+o1_UvM;J5@*#!0AnatXY!!evvk>u#C`L>*+7#_-k+-0FC>2rwLE{Nsj|$*nA_ zGV?`?bTzT7q*!dw!Z20e)BZ^-z`21kQh-WEypTP7_l;x6I+#(Z&}odLonM1q$=-dL zn%4qJMn@h}pkm;NWfF6blrLFPz3D0fLwSSR1b{HtlnmD}t=oWgX1TFuksPAoj7YAw zhlx38EHj@gIq<-ETF7~doCtK*;Rmmo=D2Hwn?RH>i^%PPiWa1KZ$$msdJRWiBB#t${!3lflf@gHI^bX%EH zfU`d`iz=;EeI0T4bR6~Lf|)6!vORyv=tNxiUvaCjDsEBJ4(Ih7^Ib+whpS^6WaIIx zkIFG{Eqdlhq{2q5TC@ZAPRC$JosFr?>inK(f6Qa2drg# zaR*v#cog!WoEA`*@)7MvhkU`lpgW#@x?jT{U#c*XU{LOmD;TKOHVf+cJ=I1sxe+mo_d*edZzJUK<=qP?`IW|<(qhZ5GO02XxYIiNM^`$gO( zruUz~PksC zp5IKEL!|mS3P<-^t;XH<7bjXmbc4M|{c|+$8iQb30$Cg^BS@zm;wfZx=feYsp&C{+ zIRpo8_%Pk~`9$GNBu8$Yq8dtzdx#*J3H$G4>r@z&Fp*RjY{GoOP?N5AVNsT<>t5pU zi_Syo6Y-t$+Ob}5vFf$~F#zs)E`7n3%0pDcF}`$7C}^uEZ8VE-9=)0|UW3-7T*>*1 zN-$T9@ihv`X3R52rd-|R?L~~^f@k8jV>%UGI_OIqA5A-0wM_i9l47_aS{7;CyEp#I zx+BF5US29{XXg+OP_eeLTz*|W*-hxNmp*pR{%gQS6<*xUW8MWp1;GD~29^|5C;@IG zh^eK2!7(;%Ki9sR-vu}3p>jYDW0y(G+0gVu`8?i{fQRLdMX8)X+0laM_>n1$h%6 zuXEfR?hDii3f2g(S{lIbV5sU)VoJTr7E0y}PDvo&lN)zO z#XpAy*o1k7m#V!wL`FQ=;AqogfD?{Rb?hsa*+Yz2@VG!IQBa^J!3KjxUNijtN2Nv+ zqZqOa1LZxF2QYC(E3Z_#G6W5NTmF-VrxAHD&W;+WREw?$7x;2b5kD?KsJ-K6o@>~Vn)5W4B+&06AzmjU`7XsE953^p z>xeu6t!D~jtGeKdIQQ64Wp@hqzQo$*(F zZ)`;Q0w_D@_ex6AQG+(tHIzc3S<@UG3!aup3l-G5(Hrsb6n$4&R1U}0iWZHEQ}=7& z(3Yt>cyARg2lu|KVtr>Xk12H=yQdxxQ>{^IwG5(mrM_a$s2~o3OudCLhv$$$xFkmx zq2Y>ZsMo6cr#cdQA672oR1a6GexI$`XYx+v3gAo0w5olkRp?>4ef$4OhS87BIxqE? zx_QMrmyn0Ir39OLpuyqVeWl8(WC>7TX{t0xIN&bi|7F~YWn93br9iNIZrSZffa9e* z)E~x@)W98-B6&qj_1dD0Zdgn;;Ir6c{h3e3rq74KlXOYIu1?gE#Oc5D6d8P%Ro~9~ z1E>h+TL<4{M`mlD%rh8IDRrtfdSu@^hR2MGsD;IE1?0%Q9VQ9--um&TBb_e zT}#*Jd|8bgIYU(?now#!P+MW54_u&+rJ`0#s1MYr%x|k-Zjqll#(bi72WZ@l@43Er z`BSE*DAb60t5^2IuX}4%_Gg^eZkp+N#@rf2e*5z{g_4Z&W0$Brf;m_~t~6pMZ7bm; zv}fhE_94lL(*hFY|2%#&t#9^g-<#dM(?}EW7gWAxU^OY}>*4B%sxIeY2EI6K+t4J7 zRKj{xAS{N3Ub_u8hd-YXl8-_0jcC)*PV-kvZK*L z0kvTigy@8~w9q%L*g(QNpjH&ViM^i}*Ro3wVO?78(UD4y}KTjU=bkRxZnfYt>{8Op1gWh9dKZhZ8piUT`^~ z)?i3A9W2Mk?bF@yOA{-uDuWc0DHLIT?xMndgUE~`YY0vj!O{kq(KpxzcMTVG%xWYj z-mTB<;zWJFi+Mc2o9KL8_rB!ru8+RmFA0qX?*Vt^&4aph1p4e-5l1rz)&a1DK2z|bpj@@7VV7&FN!1zW=UQ9tFu#i_ZXvO_4 z@DKf8D9?ieS@4}jU=6arhCUyoyt@n_N<6<{e|RRke4un64a>Jc`t>al?%@+22?{3d zSY#fdvAkxg0NIH<)Rd}yjQCeFPf2Lzebr{mRQ+=-uTZ_64R$Lx*=paivc}eQc##M0 z&oWT>=)c)`zD4g zFPUsI5qD9cB(TGT?Tdn8@MVAQPXRA3`q72XM$dj#1SqEKshrJbd-@bjwXo!TLR@L# zpQITB;* zEPdGh4eUq)c5J9{i_ZGDhm5vJ&w+WG{5U0y_^|F|ueAd-00~NjII_9A_S9^jBb)LX zwmjQ_@w?r<+Ir2y#OtY~zrEW4cVuDipCLuQ8Ft~_IZS{==#;dKb|_y5S)Tuy0H%q4 z$1ot8`&d%Pz<@9DJ2v>t3=C`=4K5O8pHlo1kgZMp8ffo8L#+!)iqS_(2 zb=WbSwu;%g`{T$*F2gk6OFtN}?-b<7wTZi$e!R^R1Or1|92vDOLr8d&Ktm9YZ8gt@ zODNq|WHLgeP>66GcC|NJn^jFo-wXZ^7$d-jf#evJ2<0t+{Xo{TNlguv2aJckzs#ThSbiB0$2Wdv;zsLAC!U)!ttvXq90e=#sH~>2vS{1faHEI?R>-8 zRNbb3N&t80=Y@O4d6xFKb$_d`kU9|Dq^AsPbpmB*9f&zNmUX=^OHtl=Y~+Z+XSuF0Y|=#&c;t~^C3AxOxo zp)soX_Oywj@1l5m4>&c{RC&J?-*NWUl=`ySgxY^0LS`NBqP4lKVwHly04gqjts{ey zeIJtP7>Q#VY^|aw(y3@6Sw|B^7WQSVuvzpo&+>~cZI^SHu4?of*f2gq|~nzbGnBTN>z*ql`?Hc6||x;{njo|-%sFK!l-FOII2I_`IhvxLE4}D z9T4x#@h>sjGcJXgUXP|h04>ibLO@W07MXx}pq%!b@LH1-6T0L)=aHuMA-xca#&<}| z`b~DHYjgEO72nUr-jg7E%*ArZU}pgf^LJC>aamQNN$ z`8ukgB`Cu8hc-psS6)0mL^%zRFEZ13J0hT?6c-4sEc9`46LWAF*l{GY`rBZTGq8#I zcvJ@^DoVV0t?KyrnmMMW8-uz)XZ#1Q!;Lgn<$_xr6@$f_Bz<3)S;gvL+`H*dF@=50 zD5ezN8}lWs6dE!u(0NfLE9T^&Hr9}~?0z8R>$~@FiiX`wxl#~kt(+lM!r%w{yt{X9 zA{HjDTz=!TP*K_t__C#6n-Rm@t4yxhp~bXMo{=BL#2Lb^S2`oh_*DM0asR7rIwSKq zp5>-xDr3=eUFGdq{Szn7;nB2Du+gUMt;@IDTg$FlCI{kgK%9p5KRt2U#=9S0Yvqq$ z1#%s@bNtACouC@#s^ize2&SbRC>bjnA`#)~8>ZxJhAW1JHC5@O13}3x%cT6fl_m%2 za`R+Fyyc?@>3WSR7ai<>Cv)t7+PzoZ<%X{hBf6Js(+XWsF~prxIAMMh1tDM z9CzyRF++Fe8s*$1SiFj z(PBe31GG94xXz^q+IK)L&ZMKs|2#6}k2+tEsQJP$P5aTyl3lo$y$br+{EFC?8Gq4tTJGp=#m^Eb_H&F%c zc@gblc79oGW0d4?Vh?{@|{&ulONr3E-;R&ohTzhz21=IeB0$dRj@LC=X=u(apD zyA`81nCB}m#0b+So{JFstvRo{P^+R_#M6k%Q{~n0y3)nB;0q?rjwE%!EO~H^u!)G8 zB@@lJ2Cqb~2(LCJRIQ%dc2d>BQ$6LaN{hjsbE+ca@l{UEAc{lEts=pT{n4FB>lLtD zD5!u|LHL(cL{t@FeS{a({!EncloMeo`)|)D`s+rgDd&87xpoIUoG-(a>cX7&8`sm~ zFJUEXZLk!I6v=jGqpt)f8{g_#B)B3CPbK}Ft(OHTi%LfB=OGF90MIUgl7AF1@50W% zRdiI0{+pqZ@ol}Rj-Y1r2sc!Uml`N(gBu&l%e>T)AyFr*w#Nx{S=h}|%q`~?GHRnA z+D2#uNmxtC(#u)Adax8ARO)ncJv&oIDL^^>+DR^q{{Uxbq0^aZ|K?>c$-h;2r0(T) z+!~k7s`4^BmGvR$6%y|iQgD*=~D{H=i<_iNyTk>TTe%9z~>5zQ~?tsPPUkF`Vu?0cT5rf9@TiXuQcxcAVSKeAM`+kv@uaVmf zbRo>DMo3;ep~r!~T(Cp6;D|e_AOwZrtz87r7mHbFfW0HSew#k?8_?T&^<}c3FEfUw zb4;;6STi*8icU9DrUrn0>c zEcC5`qOKV9XS8ouU%iWK~T}3wCflU z<|PW=mA(_sCS_I6%iyhJ^>PSO*hPziZq&J{?| zZM~YZrRW+fzxH+Kc~7DrLeeDl8%e(=U5v=7asRRm^+ihvSyR^V9JP0{3KRd_ym!zY z7Oq{#vA$D;(EHb>0vu$=yb0E`Ghxz0WT~VANlrQxGeC2we^WX-|JzD2qNAM5Qp}H- zqnBzS9@QO5O?gu%>6Rz!H2e8;3#4<6;(!R@+W^_5Z1z~kX@r+$rr4_=9C?C>Cu_oZ z{FVX1ey(w4_QV-)@^wvogZS?%>N!nIkUWoT2q~rR5JQN}PQ^U2?jY)XlJ&fy zZa;}*E3kRF-UD@~PKIP}PJD{NZmsEF7v%^U#pj6Ej$Bo!^kuzwv46=@6~?2s(GkOm zA%M9Fw@bdjc)5Rd2aioT4TebRVNr6+{8YHQI4(s`&i7AqX(P26=UldpMyBTOPYW( z*$G#em;nsAdfTRxALX3;Y1WZbYN+_U-iZxSU!iktCEu3%WgYivr+i+`^kFQSa4xw- zmQfWCL4Bt@t|xA(g7^;CxbR=6PalygwD~7BpSX4u$e+c$l3_7rcKe-SS$715h##1p zHvp{a(S@VVzy5JwYNMX^-Y-+dq)hEPcgF@Suc9F{9tfS}UZ!5>O&roOXr?+5;|Qi1 zNtpr@VRYo9bmHbvbz-I~(#Z(p7igx`XKa+-buA@~7Sxx7E7B>-u5Uh=z@BH;B@^v% z-81iRS2SP=Lhlbx^;}Z;OVqTCRofbkAiz3%>wLyo^jUJxc$Qgt@*F=ZqFeKVt8^`J z;tw`|BM=Olw5bn3l5$O4qeSZ13A`F z3UhCBm9C$M-|vh#dp05L0%>sqbDt+aWtuPMnB7)443&ffNlEs*26jn;7Y6^HwOa4T zp0>`{|Ct@TR9@TkH4jO1?&#PYGMcSzk;2j-X)k8|&S}`bJjf;g-=Mqs3CU{H-Uq&ilgWOYUZ16`;%bNQd>} z=;HDH{rPUTvf8`M?{qAR$LzG8O**v%WPCRa2L&e#rWmpItGd%(7{~Ph);mJl_u|)u z|2#*^Rz&rQ)$0%14OfBKc0aRy_`_tzge}q5Tkk_31N&!|qnF?JV~Ipim98p&ut&uf z|CWSfY%77oC$ghFxfh|`kH_GUj~AXOKZm96G50sL|KN<)oh#5wKYQwjssE3j=QG_! z-weS6f%d=64$0aOG$|*8Fo-ECVi0V=1)Gy@8VBRBJO1zhlBsZ@UIO;s_bd4G`H6a~ zT?)A~4)^pYal(t31}iCP#cr3s(lBHq{d?oBN8@Q}thW=HD}QxCJ8b@DPaa6+j@9@A zN4@NyF3Kx!&LE-wBYwwS;pb~eZod6qtm_(G-tW)d`@N(fRm7Olso?|1=|BN(gs~O0 zcn2}WP+cy%69dmHGyJ%K?7Q#5C1gtmV^JmS_zPDUNPn)>IJ0}1k)*FH(S3BC1&?}{ z97X)1y&O?X5@TYbqDG^RIzeAT_l0JxI;al13wO-El|ZH#ibJdehta;OY6yX?w~ zbxC&NslU~}*`?6n=UrgBDWuZXP|x$JL1F!yElY14we_VyYwuj_nl|KE(W$3iG|*wb z5AyXWpLND6oYkaIk)O?wjN2#YwtTCldsKEcjeoq1yeLpwaphQ)X*Q=rY3jE$q3B$4 z|BAypJNvBBoG|N!6km4k@48n+0S~I@4-bh<`SSyU2IvqSvT{&h$*}wz<3@h!;nb=G zw{zhkwa%ubLb0;Z?>SR+-O&l~6^}7Aw%VWiE@@%fZDSnNu3q7Wd*jYGX1pe&(^^eh zg2elsi#tO!Ao-$R+ECwC$ZkdZm)KvULY6>j52hY8$pqtyw{`#z%)7zj8*?uDrF8M` z{a062SZBk>LnqxJ5sefd2?!lT>8(_-R%aLBO{y!d$NO^(Xo3pTKafI#VZox=82~ zIxT%kc+-`}<3IRjZjF{%gGa>(>K4&N`%BJ$Pzxt(oKbhRWFGGE`3r@<;%kO_O#-eN z@HVYbGJS2CK}aICphibn@qImp{G`zQ#9dP_2%FcAPk@bMwQ^v&GaTWhUIo5zC<~l}nj$}>Z&E$Yv1*0H zPNbz;!IgxrS}}#k&4Hm~fE)mQ_39A265WS=>^pwQtT0c?iotn*w^iX@wYB)|Y|FvyNL z@i;tV6C`#t?uHyv#_J{fH_Q&7vG|jFk!dKCEb`@9S-0$jk?CM!;#CPf%GlguKrm4u z&Eb|ofPp}w_Jot>C50%lFTmp5teX5>2JMCbdK;cc-e{3#3k9@qK1!2wr#D@u>T&s4 zr2Zr~2=A?v1;x32*IJ59j-WS8vM8 zq2X)l_28EkUA_=vggwG{?2Q-=sqXOZfwLzzzmTbIJs-sGA>Su@ZeVKXOI+v_d_n_+ z0rCP!l5Viyn4A#h9wf`Q3p*2lQi~Bf80sW;K9pdLlpVi}e()O1H=?`Tvp>*r!{8U^+xTxM-0~{t*WAjK_9nkV}`XF!*DuHgx9ou zCE%+t3Li1%wO|k%jJm5HA0x<5YsE3r!^WgxRo&ckdyVyo7Ai4VTx*)VE{8`g?qtxV`f>YR18_(n_A)YX0_n!ONx2L%YP;d3fu( z0P%Qy4bQmqj!uEtd|jAmI2WygL_=NJm}^q>_D;qb^D2#@sg zva)L-jtXE%EDiJFed424F}-+zx>0lGP#p!_6)kd+`w+O0@? zV+F@a)!E(}(V4L_Q8ip$x=h+tl7JWdD3+#FDJor_qRb0|#tzS!x!T)wXx3{`y6l2( z1?(1!61zMh2(9^=&m7rr5CnUB#j;gACtINbQ&!C?2~^MEZS~}6h2PLqj5J>(XFN)* zv=f=0`C)GjGiT$OjWLYXL5RZ=)`FeJ3)F8z*-&iJ7+Fm^bH@D3F-jM+Ov}02>`#-{ z$?nsi#BVqrlRd(?^JV^#I*%HF`u%pBumG+Z!;5#9Wxa^D&$H{y3`C$VsaAe!g8kgz z=NEb-4YX49gZO;f^x}0KDS&b~nTZ^jhyDH} z@5q_2Or0DyufTQmJ9doyyCM|577%qz)_R3soHeKmd5>TMV(Q)lyPm)G|I2B?`Ur;B zQs@f%FmmIQ2>pOk*yYPM?<43>#aInZtG~s`{ibf|)+e?|1+5{X9_7qp3jZ6oNXbAe z8gSsZTQmK=G3M)iVN$vIke-n)U=7(t*o>3+T#X63tIwKY zr#^(KZ7d)rXb>0l0Tya#b=7Z{;(sm8^n#CXLKjvw3W7zUI?4-&EIgu`AO%3HunJ&@ zTD}wDMOFJ;m^p;P%u-VKl}0Vke93fz01p{CKW!6vw%bk#cCm6 zn`wexkk+P7Wfy}{qv{{e{qX#;pJO?dP28TerNRa0MLS0*sHF)}DzGoba(~k6;!#i# zQut#c_{3CI*lCb%p4Fpn0%2n5_+XHvCYUynQp&P8GJ>kh1nkJk>$R;D<aU2q5nu{lHq!u@}1*PnAdfbXZ)Jn5q|&HHF_SdQh> zM<~P;jM$JR2m(R=fR;Q6f`nKUHsF&hnwCcPwO=+|&}Kv+Aw&uAdH&q|cFk2r_HeNF3dVD-+FnM*bFKn5R` z2f{@7P}AF3Tf$_@E^;iwxQPN>L>sPMVd@|2AOs11rfV$akv+m+yRbzw(4Z@_F8ZS` z(hL)QA1tKNLu#b9>xO+V-5Z{rJ}D&m9ULSQkovuze!S$CI0mH*@!u6*`^wJu-wVK5J&tU`xr9lJ@Bem=AQw2a^S4#931v` zwyTRBZXPb)Dj zhPpgf>Th%PAiPB($!&F2i2rJeq#1Of7Q;dq?VU-;qI=1%=n7){->?#6@J(BTPb^~^ zD|a)35((^Pzcp)t$_~B;4&K_d@tgn}3~Bn~Epl_`;<7PI(F+ zIG*w@c~`d6Q0P4Il1Yi`$mDCHS^nNDrIzpAy0vRe{KEZh%0_bt4C9IoNqp~Z1p=jV zr!oYKtz(4OmCQ>e?%XiM*x~J?{UJvAqF`OHfHE$E9dX(={AZ`m&8EJpo11vKnQA!1 zM5`dm*ux4k?BgB6fR#2sbY`YU!$40o&?T1afzE4Hx^Aq)w?K=8dGx)7oIf`9fw;of zO9ZRYTEnIhy)o1fJGXy#Ak4(TH0+{hyDRd%smRWfO6~W8m_%Gu%3&Uz68Y4;svMkc zYa^-OnIRuW)SS@VKCU=;f^mUrPWvoojFyJ6@J4*=nI(6wa zCrd0~94 z>mViBf|oES_dBs<&!j~-_Z%)dA&+tAFeG zscZeI8KfnF`^Mwvui=WY%RwLr48*v;QtQuNdpy-f=WLhoyd&hy)Dq2ip2LMhh>qYW>>t4K$QO@DKR^WNZ z`N-d^Z&5KknKfHlVC)vStHo1itFqDFTCX!QIg83zmo{b(zw%?$w-uJJrSwsz?W9QL+SNvg9qAP?ob!))_y5?Zq!=(#c7Dpz@QqjIx_YG_o zid6jJWHTqGnL4)X~+SMVJy5>z*bvx*D zBaYE`k2(jX`Q=vDel1TV(L6fS*Zg=^pCqOBKT`{8AG7P0IAYTPuNxjn`E#OeelN6~_RPLpLmVE7o zzaP07zM}aH7Y3l)DFuV)HcvJNnq;*w%>+p4<5rgPiETC~SjSgvH5?lt;k?%g_`ad= zMFz-kjn6sq!IK}mbZvWLp29|cNJj?txwEgaGfjgD3Ph`A&e$R9YKhXc+&FQOc}U=O zY*!fA!YpQnxD+YxX(X;PTM{>#BUHt*J|WB}r5m_p{sc07&5FkUI{)fbF)8=EKw zLR+jqDk^*pFF>Qr&g62k-G%s)_%-F6-9=ur6LYho=hRM&mbfImv7w)3bp)AMx-2w; zr4~r!D6{B+5Q|OL`jNI^jQHD$U)Od^G*x)JnBFf`!7(S41H0(HlqlFX_nS@bZ56HM zXolhl-qLIYr&0xh=dsR>L-DKpq-!6){B1k?IXbSpRf~8(C1iWfyd{=yZpHT@&(U2g z510zlk$b)IF8^G1!{+6@Il0_Fh;1|?8sfn3mS5K~4m3@_IIsEj=lY+Iuvj}5>CCCD zzOf8BUl&!Wf)g*GIR8s>A)Y{Sh7_@%(_;>3#~kygVb1ap2s7?BZnG~_YwIgmwrP`< z3nv&d@9vZ23ttH}DrE%m5ESEHs>*siagipqBtKmm*F0lzv$34(XUQ~ zPg8JjuotcprT!Oep8LBD2#F#H31q^y?ChylZAk~{DlKir2KBJVQHy4b!vMdBk>;AL zvO$8xYNvy_UY802n}7?M$zV(FBx)&~-f{9`Z9Hf7)X6ILej2;yztK<PgS2pV-a-fo6D`-$=fs*wrgy_B0IH_LH-`t42c7!c6Awh9G{s}PN-dOI@m(2V| zg&TPjSfXgUpMtE&MfkEcqIa^-c4y%_KmW}%cjl6EVq*B&s zCto|OUr$7b2t9{6LVumwNe7qYdm@pzA|k6DzEaQC4JU|mvw43u6gsJU9Jw>3*_wm| zOSSGHBOUAdJ;DE)VN(ut4UsZQxeJ7l#Cz&JhGZ5zb?md7IoXYCpSQc#uITHUGTIwA z59%))F@i3F&lvbICP(YB=!rvju74+zo>8zDs|b7xAuv|$B29eV?rZmPrsERWiQ$E@bpLhc$`fmy0A7`47 zU%%>&PUfHv-y4TVN1#D%bVt<30I=5$Q?g}(47WCDn!b7c zVO^75f>Z-nmiXXXLC9S;_Q4{we_JUAqSxgr4Ap8BoKQ)pI-;x{h)_t}Mzq#7^t3?n z!2$_k#v|28`T(E02y^bIy>5|V@9C{j7nbO@a8f^+Bg4)F7P!jc+^7P>R@~bOzQ8}< z<92DykLrfMoBTk7oc{@{M1>}q9MkKrqd>B;$jZnp68>sGDdm&U#wEo`&7!9iF)oj^ zqpbp)NQaPK9z4oScX|d#eP^5-K>aC5Qy-QXHb6?&sQ`X)?zUC|&jqMpOWA_VJG;4+ z!F_|vPNGF$BdkUsMTU)ejA4iGU4KL}IToZEm2DcTO<9jEv#4?&Fc5p^j6oh!s26(C zYU;p3o}eeSUgakF7Jh}x)AbLf_7#}Wl>~32yBwsw>7d zrid&j<^V!?)kG_iJb1p80Pk_Csf)uGRHOM2)7E$kdpp3^F!G@ z3^QIUq?Gs?FF(<=i8z~>nsY!Z9!?aW6c&5eDIVYs0r{I&5hKQpvx>#<?Y6f_z;o6A;G1DSRrV#i z7@yH9+5d@F|I>6$oU%Rq30JITAVZdv^oD1coLtXrVMXSlQXM71w)|mesS)Z>_S- zudFU?daK09@bp^(8H=-1yOt z!79uv?!1k6o6D&^gIWEfZ*HWc`;vUx9=E-k#)S-NVLwiJyw=(Zb|l#vrFyW!5$D`I zoJ9=1&4zbz&X;p!=h=s6<;ql_ri&->`k{e7x4je14tsBqtIEPI&c;mbH7LCczV903 zOoDszi{b|vCT_1*zXD(SHYMXwpF{)8%Z|JXOIUNK8w~hnXRVl*>~0)hl>gv)@$AOV z0E`n;L3o(Xf(i}IPa$*Z%ndjG6ZALEn9qANo&QoQ)-m>OqPBx`;TXieYiZ|aRRLi2 zwR7FFK*I*O6aS-JrU@kO~cboYD7G8qZ
Z(MAJk6C5f4)`tfG5c{2{o8g}(G*9F%3W_Mk~d((G$? zpd&Fo-@g@+5B($|IqHyxDxHvJoV_$hgCP zt=Y?Dx!8(-%AMJ=C?JyY zVTMiO9C`j-UTd;-CRp6{t-DKtiT!$Mm6BhlC?FFaei;0rR?1(nW7JmO5(~iK(ldNI26`ocuqs-myEgaNE|d*r?d4*s0jIor-PScw*bOZQHi( zRGcSvo$R$&Yv*m}{Wj;kf54c%-+lC}`mVC1{iOrjF?_H3sDTLt>Zo5@Lc)4Hy2pBP zuy6u6J?Nn3T%B{|ZppEMkw{Aim^wG9HZF*cZW}l`RyQ)@M`1$@_({u;Bw74uf!oPw zeF&e!ej5;t2}Fr4kG9*({H_+^m9LC;JJe$9iN2Xz_LzwkSoaWfCH43^lG$XLh#{{<|CQ=(X$62ic&NI|rIS)qQLu6^J(+eG?nkgqV)PkTl%bMo#Y*Qe>`> zN*fnO;Y{Ks)tI)$AVo2-=(63mJ5#5eZ|0?3hNheyZMzdDDx7p+BY%!Bi#oc_2kzsw z-fI|28ODe(qcHcHnmr{6hW_mewKF$eD168 z@^IlKpa1#OZsWjpnhVDC*)^7mGRrwJMbh!49Y$}6(T$b0gWCBW-=saG^kmp3NuwqEBJ2MvRX*KS!2@q{fuv|+^>=yF?J(G#c=M`{#Fy0(O)~v zi6)nVU~N;>9oG$5F8x(;-xJeCkrXRC!4zwPz@lkqIz6Lq2#0e_f~Vy`#2@2z4x0FqFYdpaIR^?Z&ew0&P*Tt!6FSI#GJvZCpaM^@s<~Y z25FWg_aDxb->Xd;g+ZgKOyRG;_uzE$4E*|RYas8=b$zG+T4&ho0HZSaH8}&Dx-lT9 zsQCV3zzUjF7BR)YpICB=41fj&ndzQO_2&XV^2=ol;>;0v*jb*y0)IcEuGxP6J&%y! zC}7M2x153GtX~NiR^6rp%5Z7j12}s*mHpGNKF1%Bt_8@e^)9@q-_`1kk>Sv&58LL# zXljd&P*3>)LI=(3QdLk?iGx2;2cIfml2Q9c5{L9pfD{s>dmJxt_CslRq#hv}m=DGP z?tXAKMr_fCZjfo2a@kpzwUHjsA&D4_6<8CLE%0&8%7U<}=6s#v`{y=Te3?vtwOe}6 zd5+W5sD_;0f1odjT=n=|Za~rwYSvDMk7nUYiCU9+?*G^?Wza|3aPU-mJZ`;2sB>NCl?LKlxy{?hC>$ ze>}LK;U4-086-6dff;Y-x+*^Y0Nr-f^q)cRW7TqAI&D|Oob+VO{^xQrhz%rJPToG& z38M=AIJ8#eizEvpv!47og?c;Wr+S9}`ay zEM74vj|?%s(E*|1TaUoIxMVxk^-E;YlEW5YW_O?P_&Kp?!tAzm)S|XUrqmgD;%^vZhiKENGHem~dB}>E)#3$%glaLxGX|Sh zCkn3BE=5HuLP7)W1Sww>l9`Me^EsGL*_TtM%zu!u>Eo@l2|d<`WAI3(P^SxY76!Bc zoaFQ#U3{VbDPg`3ChSqK^-`c#y)EQqYZVQ5I65=5v+AmOGi%O9*w4}y)mo!Rv_C6Z zV-Z_SkZ?T1cNU;-kj3u)2k!|14P1u|UOCigcwO1`8NZ~EVe7L_+j4$QK_ye{I@v1l z@2S1*YJRdFy;gTe9~b2+@+&au#LDz17kC%GV{k?Qu0U*jfWyr0 zT0k~XFwwqy{PRh89)M-aMV@NAdoZcK{ejNV9kxMjZ zsbXybI8Lrw^=Mi%jzqqRouDHjjNa@imLX4fa5AYSC4}>_Ch7DIPg=~>eZzVX)&oav zqMdN^`kzD&5H*;Yb@JALk)X8zEm0C%Of4Yed=U3A8y*H7$Am|0glq6C0dXS=;pr7s zaWs~R$tB0m>BO4D)(DTpi5!q1rHGxXiO}9|=pFqZDN^iSVAq*5)1$3aM$+(c(#B?T z+XQWOJm#8|x>9+=Ia!gPjHCMK`o z)a2M>z2?~T25INgSJ}|0s8=%y{+e<@7t>;*L{ia9--)!-FzQ%`aL4*%Uf9~p!?xC+ z?5$4ap-@FT5|+{L(U9VN=-2srui}>9v>|jfag}{%#C(z?pp^=yHm?-v9;V{Hlb+_+ zG%QK`#-9kEWTim>-TA=S;SSp!R|YCF)CO&-XShTTs4+skI&|=OXzV*8dMo9xUp1^` z|30{0Pbpx!eQ8+o-mm8T_J70k!feJ3;)uVV9_QB7y9M{ushQ1M_CDg>LGhSg_xa86 z-zCm+vlg6k;4nDHxHmdIG>Sc3W(AWhWY(K!w{x1#aJ>bna{BU`CRx(Ph9)oz+y`15ndL(5+!3Kr*F;k-IjXIm77IXOOautZ>5KoYpQC zRdka+1K@dZpBbVmvzDSOVcbxdia)rVO}bsE&PN zbzzJ0%9b&?a%_2fNP1h5YC`=9G*gld4WnUOD$9gt;x+t{&Ezs&>%>00j#L^(_XGTS z5zN#-JfA|@p>lC&!V zsRxM;9U%DwS0m|Rx=YsqVY5Q2ZQz$YuBfz=6=L@GDOloCnfj4@l)b%qO-|zgugSUHA;lYU}9jCju zGC)7)Mmte22Nd#Su=>7G&vz6>InTIRL^%XXf`ynrE|AKlsUmVzWLP0vD@IWgGUS z60%(ZNmwOAAmw0!K9piVD~uenYMbEYp$vidu+bFq?vByG!YuZXPO|!<%onT%l0qiv zImPcdybUR)LI2drWEFul32e>c0xgJ;ohz0VV@sCb?ZW`W?_aRa-;YRbPh3sB^8i!v zJ7~L5{SmLq1vnCv=`LCytL_-dX)@EWpPU()lSfQWJWIL1EoK;!X%%A72Cq*ol$;BW zc{4<7A6dkeu?Ze`v$Gah<@@|cF!eJSP8_lE3U9=?eX=wqV2jW-uMP|HG=3b$B1XSk zq~@j1)4Em%@ZN$V&i#Fkf;vNjUIF0e9UxCfUF7r_@w|xN-7n(ZXoyWBaol3{tdAm_ zsu8uks{MMb`c((&fj&{Iv(5WHC=}Wjf&a{l{E?EK1obx{$aVtf*%r6V%zA4??PgC0 zNf0m8A?OyUipKbxO>UlXVnl8GFHurAZdf~+Tnc!#kA61CM%fouj8|~ZZ3Muhb_V=C zQX9R2|4LWz)>@>>JMxdNf)>pvA&jhVZ+l<1ioP*KCk{l&Q5+uCH2=>LFP}+hnAlsf z+`S9bLBk{Zl#amz*bSixlFRG+MX|KqHL3g-iivc;T6x4t|IWYH&|wDcO;NlC|A2LP zxn(!O9=7p3lQ!wwTJOxX4=8|q5Z`(4Tzf6+_SPo^jg+4!C6nFH-3^@6hig}#nZ!!f z-1aC2S;P=oVuT4;Lj3dxv<)I%A%dQf@cUdLKr*pgR;8DimFKt-`J;QRQy@2!ZYD-} zrIyHzWPx7E7_CX9;sy1QSHvF_6YL^xF66xTN%vf9Vl%hGdU@PvbRYn?TgYfBmdIN) z><#8o)LZZwIC6KA9sG|lDE$ro3I!5NtFVLy1(FP&akkGKrzGWH^bTu0GP@| z$JS!bAHTE*k@QCx)JD)xk+;|2@KEU2_T^p!pRijn`MWI9;lv#x&l;}rggZ3n>+f|z zC}=i)Fkv%@A3wlgfBZmHkOl)s`vDCN{e!j^Tr~9?l0=kh-vGSoD&z5mw9~nKCq6k8!#CahNLKtaW zpOgecxc(5R^b@&;gzKRxDnW~-&CpxLiWxAtfYq|PVA;^J3l1=liva zA=#VWxh|j%|CM2Eb~=;A>2$)q%V}n-yLSIq?gz!5G~tAOI1-UIb>^hAHsE^L;H8{U zZBL5OH>A?osTpZ?PxE)p;G^TgDHq$T{vPD|7UijJ!t}J#0|tJk59L60L%S@S*3s=1 z*Y#~aR`2b;9Q;^(=h5M|W22Yzhtg;_U(%kM(x*>DyIw}4>L?hWx%*tG-XT%@p20f& zM7zy~$0-iS&!DSQf$HtD`2oCB33{u_QY@U!}6<2d{EGna4 z$GOr*ksUsEGF6av8}b4#S!yQlq~1szf0C;-9PDr}=q66DGek##vC=I>kJczp-=Z{a zrru62eveceMV()IJ9qD(+2D2B__~k&BX4Jxp{Wh5G3QQ}ArKDr1Js)8R4!-7P@wX1 zxk8JKY_!VLyI#A`}34yfZ0VI_!QIR7cW&(_+3h0U+q+yR3vbVo8;b6 z^}S^-D^9lceoeP0Uvls#fXjma(S_nLoFzV|>rwy~QyPZ;p+xxtQ$%?vi#$iNm3+f| zZs{|1nPRbdd`E;94v;9)I=Evx&@-dE%X@j7_{8eG;$Nka#F?SZ#mtgfA&=#B%rhJd z(WGyzs;kVk(GhZI2?$9yyf)(UEi@{P!O3Z*+Q|tk@ltN8x!7!f$+LbDV_KNT=)Mdm zJ2e@*I*X`DtQSUh!AX(PU_>OZYKF5m#$TAh0~&^o@AS+S0?wM2mt`LhmB*W9Eh;-p zEma$4EhSX7vd5d1dU*xlDfOXfe0pMBtD{F3@E?4nCXUyl!F0yK>m}8SmF6FrxH=E& z(;n(_)@Af}T16*QZlWAZ43;e`S$UnFWXKszNw}Cq*Hx_Rc~l~z=%@E9=KqMMh#E^d zJ{A>Hg*(!m0VbAFhE3r400pg9wyt9qWQMSb#rVLcDShm-;qja{d=bpm*z#wb`w*Qocd5fqv$L} z`<2C1okBMx`7}~V(w~vf!$#fd49X}qER7Nr=q98WfGUyBhr;KcS4W$qFI^QJY~0O2Iae+MOpf=>%8%Z_$Mz_&^&9sEb%*KJY#lGM6`_kH6^{?2Na;lf zAHaC4_A%YJ3(WCarTsH=KE6fc&d;7z)WZFbetQuLl2cIG`O8B_$5| z6)x}WZMmoSGSzpA5|j!I_GkS&)$FbEh)IbB$i@MWK&rE#_h}wiQDezw70)}uz2^}& zCZs>vnW~w4M>VqzUG69;ZMCYs@}7>Sqrp&NovE}y<1mylWoB59 z^q8a>X|un@I2;M0*%1`yGb${6k(O&DTP?x@<=K zA#?4S>uqUHf>s>gooY<1Xt=fAxeDU6P3@$fGDwh;CV){toF0kcK=58MtP013DG>dM zVd7o=X%D&RVPR9fbiHpx?qXG3QFI&&Q12fgreUQC&_)g)8+>oi>qx{TOrAX@l)0Pk zkNAx2%E6)cK)tZCJgGLXvf%2j@fbb`k?^^XXjgS{SypwzY&f~PYHqIzgs@>~G;p*11+LxH{ACoOA?WM3wz9UBuMX(zd0sq6ZuSOu3%T zBkby|1!k9G_A+I$!6#l_Pmb9m!8T#}RByFzH_y?>XP-R}+j8AjrbnGRUg&a=0^r|Rr&_`mIfQN4=UUJr1BXogpQ+VG524B zpjM^dXs-tH3cJA;f}esfOczM3yy@b-f#|bXB0Cd8^YgNb?4h!=u1843zg*B%JOg)h zI5_{}h#0J$!oSM`<~p6=CIWO9d%;BMO6&?R3HZYtY{axmxP5V_N_=|(SXiFCe|J1D z4`JciV~c@m1In=u)`NJQFa)5C>Bvbx{SL#-$>hY)O$#wf>m1egt)~Xty*rTa9bhnS z(b)qs?WtYw-mz&1oXiy%?0%WAD!j`~1>`DGD9qBnaJb<&XN&w(J#akF5|*y0i_f*Z z){7)Gpg-nW(wok;$nrhkMKW;ch za{QV768d}Z!g4v{-r_WboUylDuRdzQg~LgzQX7u6)B&aJ%mN3rQ-OErwjvay-pph# zr5T~iXOwP?qE{vz{<7BXz|0gqVL|lZZU`@xE!qywl8Tc>HBFTSq*`$+Y$-J$gsOZe zEH;r{PfrTum@ZoZDiq5{8)g`~&_V6C|q1w>+haNd%HQ&phi7k7O-4fHE9Zw9jL-3;lDeTd8 zhnKs|bO$F7nqLAG@6c_BR@*c<>NU^~*c_l+!cey8O%D*~13)xgQ`KXnMd7W6Ew@V7 z9w4hNe*rWBSi-v32Se#msov+=ekM`TKblp_+ocmq4?EZ%F*!E#@6-rb{0? zIe#0q#y*cGNAU+f^#oIn-pvU`{wa4S=MN=`@0KBLFcUA39-5cpr1&UMHTi)+ULD&{ z9Fr)5y1{WW0yIpCa4`L$7~z@>k}~(_9d8h5om&tHaNXdBAH7e-;vb5eZ_KB{>UY>6 zGU6u8Q9Se}M9&h4fhk2cWQToqxI5gVpe#!GaVIueL8z5@DA3r_L995coiFM0PxR5Y ziLrG|)ITe-ElR#JG%T1u^v5kBF-2mGSW&a}grb{QCnBu$vD99|U3TMSHrdP-2Q^Dr z;ot@-0K2Wj*yG!ejTwjRoWypOhrKAVwbZhA99!bF=%8N<`fyzIS#h>88T^5G;|8~= z<5&s*5stTlBeNP7;G3=ZfOg7m1gb^nN|T7NY}3-3*gjicQ1xTM- zeS_l9RKU4QJh#vzN|XYWTUS^mDJC3Nwiyk*97(S7QFi1zy?E?_Ap|FRWlBvOdJvG6 z9CkmK$Biw}iVKv5fo$)?~mJE{4ASr?Swep+pRFO8)FS zAX~U31wsKA%QKW1d)J(1vbE3|yeSTzrE9eGdHwx|=u4alkVK|`l;?w@>xa+8`8~Gq zyA#AncngFELq(&$idjDgL^muv;p}|rgjF}Scunm->cK6i(Bd=azGhV;O-39=5_`dg z5VzT)V!CRmj(7C@l7A#1XjD4}G4=6)BB7cKQd;KdXhv z|2d@zU?Oj;QWyVZ4(3<63B80_=1z57pinb|J|9V@#Y)K2#R$D$cVhHtxpj0#JhimP zq1T@!r+h@5M0K)&D8N|Fr;))*7=74)?!3 z^WO=7@>KmvXvoy@y>BH4qp!W}!mD;~_BHia8i$U=t4ZJ5x==?yv|r+`V3}YJLo(&3 zT-<0zJWmS`$!t`YKxRuhoTQZSCM|eY(;g`7l0|g5$WB8|MHgdo}`(tOzr zHng&S&YRg)i3T#Qrj*AoujgI2T^~2jXJ6ibJ8CmO)M>J9Sq?1#__J;C{$@Jn15R8$ zCn5OqmBYJd?mGx(mOvNQk8a$5es*n~?45eKh1I6bvbpMBBW?yB7{`MCT8vD*@R6z5k~wnM;L%O^W7T{j~L--@Q! zbU6M|T+7!R%NHa7>xU3RugV~xVqcBxOZx0~mkX5s1IMmnACUB9S3Ye=lYV1-000`X zW~olPB~#y&@8d!Fw>VsFGj*t|Td-8f5!tSu=WP7l{WhT=HuWOcNG=fsTQLgBu% zBR#P{f#YyzYC$ZJk6FfuqK@mctP|qlBf+Imwg{L>=kWvP8k*~UFx+hI^o-s#&NZsS+y#)*nqlIsnR3K;-;3d+6cGvUdK7|!R|GZeL1ZG;+nS&19g z$Ishe(Ln*mb#Nm?gqyWGGqrHJwMy5$Mn^{sfuy3w!GZHJdn|(XkD#D!nbwDc{rN}k zJa{(|u~TFAEFuN*N07QTRb9C?<|n*m+FqnM*Lb^~%y>6&*LY-PwY+R-cvnaxB$hTm zn~!QFgzD}JB1&t%>k9|t&CA%aqbS&8p$_w}^_c+JG2#%Flk{8i13T(XEmR5CQKNyO z=ab3DS1`@#GbdZ!c04F@PbJVT?w5Z0!^Ok)yY|*w3jqPP#QkU+!k9pz8p>|NZfC^! zM4Y<-4N`=wGw7J~=au1Un`)@g0VBybl6+}$w-RB2IYo@NChGDzEO{|8E2_4hj{$t% z{UN|h*-To;CFH=0xFn_xGCX^r=C@hU0wQ`!0SeDeCm0bKZC)bV12 z^YKUYBgsaaGKe|W>M#b`{6E1q`oYgo5a~bC)il53K@FQ1yx@U=A)qqg1)a`J1Pz?B;xuV?9I#_X?A11gC6x9 z@*EvnC~V-=%Zr8`PRS7gokBk(VU}J%o5$Gga!ee@tS?k5Zg8W$R;@mDlG#%FL<4<} zLm(GaUh>{oBf+$V5l+!IH(GM6N~Z+KAyY(L=vz5Ci>o6H3tCc+C2c8Z#$v;Ob{j?c znQR_KV-<@&l}_c~#zTR1L_YSjFhB}?0pJtWn@#nKPnQiLR@TO~r1@6BZ!7GUS2Ec40D z4)-T`z*!6MqxT3olJ@t90;bY-hqxEd^|^YF^$}dInuHTc4~us(6iGy^N7F*$K#*Y? zDX-Eo7OBuP8>e@?SnwF^`SJkt$BrAe;?Ny~iD*pF>nHlEBN{YoSnR=b*^6W*5WFE` z7t`SI@{80Iyvmn~C*my28H@h>!FEj6wi(0Guo{E!fcZmI-r0M?ceNJ3-tJq zHR64J21$k!J&gO5q%l z!%n_ks7WViKl@EEXDuou=jerBwlKEa&xB~ zzjFf#XV6|*DM`|?e3}T_5ebSus(RQfzyGyL8{*l#J#5JseaXf8H+l<&t(O^E>s4F6 zlU3ihnW{>MmoP+k&&Tqw%t*<$s=^;WJ&NQ>FeSu{pS_NJz|&xWh>uk3nR!Q$EhadA z>+|d)nYG=9uY;FPg)^m>t+os*TARAC(=cc)*TA?wWg2|RfGm2bV9BHYMYKQN(xUL&I!)xU!jI5MK(#nDV_a%!)#exLAe>vVVfmprZXpVU-m`|yJC0KRM!PLUke z&GXfmxlxnjoVQlW)(@#F-X>bgx))i#t(s-(bl!QH+#&&htyf-kmkoUxffVUrg%v`@ zU%ZHDd?wskxGlW2P-9TjKUw;0l~#0GGO6QFHF{bxa3*=dy4Tt{}bLqB=CXjb+Z_(P)A~Qfyv7&^}237-KwFRfQTdHz>uQ}E8 zDnD*_-vv+bPw?t~^Co%xHiN_US;Zlm1@m%+Fxw~qI(Acb`KixeQR(UKO)-f$#4#!# z^1Tl7ux!-VdH@9H7CG8AviuFBLTzx;8;f0e+4D$0jyY5bn;V&>cDLUO2^fJROHcr0 ze&Or8*p^8b)s&lvM$9tRWn3Mf8O|i7V4u)Vyl=SgT7*(g@F@|OC|mRJ#(*cB zWRJ+oK9#b}^M%OJ=6CuYFXjZ&qu#zWp^o{$dtkrd9<8qXJeet;H}b~>_*-hpHvefn zY^gE_+LZ43zz1WOy)ZR#h0Y2>(Uq4~JaGcZ>z8UPWrNFQbw67)wp4X}P#1u5LTKuPY?c@I~d&%Wct-p9OM-fQjc*2^}(eC!|umGldcE z>xt-=?Ndi>ACi``+shyq@MP-YENU9)iR!wkpB#VSUzmo!z?bW(vQw~1$h*NZ+9v=~ z)!yr+_M$p5(>MFoj|bFbmMAnAjy{|ebrY%F;me6_3#o;sCW(~t)9xb0k2Om->3-QC zDb@GM)s=KwtGE1m$9$2Hnf$YH0c*m@zNIhBfA6Z!a;*t>72oLRK$aJjCS|OPU_*i8WOKK<=qm z9;q1v#4iG1&^lu_AWP$QG#!u($nr_Lvp$hADF`XPWAY^oO+UUkCjIgYI}ixv@!M=u z6IVezbiKAeVD5S!m2les?yuvUNyn8RUw#3fogTV47$Iyx!)n^m*y`87$hA9&y_Gax zOwcQYLL?4-@`eOVbrWdXB7FrVIGNS;Em`CSD?R_(U9LHlpFMDlxd9Ytj+P$LPK5ut zF>DUIqJ?$#-ryKzc66x_=Q!lX&n4?TP@NmuU&O}dx?TWs`mS8GX=UknJ0R+!(OV`$ z#0fn{>;!z`b2xe58!q$Wy)I`^Q?@tCW{QFvuzqj~ z`C)*mNnlu#;aUv`x)j;O@vunp)ue(xL}GFmBM&wtI zZu!nxlhBUZ5Xolm)zB&)`@()5I(1W|roZXt97uW1+LWZFHEzoAnog7}e-`&8?6ND% zNmo>^o7H7=`~w83I!#zq^i{57E*?J+mH1R-@mZJLPifdbj^fjp<`i|w^l<9P2r94S=te8%L~0O z>Z5*i2(1_;Tk8+mh;&;Im|6(6vK*RP^p`d5W2{GQn!sI?hV7EA9>Oiecom}}Nny&_ z?AkzG)jxvAA%)FfVsW87#g+(B}Cij&o9ck0FZ z;-WC~Cc?UdTy%qWrLp)dS!!zZw6#y?R_9sCe1OukQkNRC8ZsQ8r`{aLGd0u!RK< zO7KI=w;X-3d8|{SrQB~#Qot}_6>Lsw&zraxTCNzXDNZgUO?S%HC4K42q}dwPTNl7J ztO4Qj=`GX8YNJUOS|;5n=BJcv=>ZM{b4J(&d7h8l72UF#vEfCqd@Qn`uMQX^)Pp(c zU)OnH&dE+pmkK|t8S`GPRyznpOPuIU`ug>By4U|Y@cU zsd!~nXsCP}T0U>kJ}KZjxjuD+2QDS$u7Jo2cV5I#Z#oyzocR5^F{u!<8e~yd$_5%A zRGW}@Me94X+nm?}AUNAkELk0vxlv$iylR2C)Ev$6-$d5637#7>T@yPa z#N9D+>r7-Plen9KkATLw1kPS?`HFLa>tyS{SjtdZ7WCZJkX%MMi>>vN^^LJJR?JQGf` zjY5{KzeHe~j9r@oa&buVN}nEv6xS<}mh{5B+h1TAj_v?TB_LIopg>9@DYO6sT~+l> zx0yeWKab}TN+C#MR%WGN>M1|@Z7==Rw1?SghTUwXx97_uIV@(uInGsqT!1ynWE#tA zrjdL!0{~amPWi~2n+{{>EP0OmcJ=%#J8x3yU%U9O)MxOl^1-N;mDTt1>a_SFUj}lS zDTl8EQw}$CdZd{W(AiMo7ZH_$OM!Wc^>mZd#zz9Z048!C&ld2wcO@IaSxh+31potFM=~XSdW&r`_+3;fxDsqeHQ5P^ z^yFr8BzHux5Ts&1gBcWEWgFs;^*Uc+cJ^BExaoQ=o`GRuH z0>Gbgv}pNMYcbx$X;Wu}yAH)3jco#ZRoZ4ZhG_)aejQi36tNa+GXZn8IV_@%Eh}$@L7weM(Or zb5iWQR6`~Tymrp@-$d+WlfPMPre==`(=K2zddcyd(Mk;ob%T3MPtGj<7Xb$3JK!64 zVhXHA>SSJ*ri`6UKZ?m#CQ=|ZB&IM@t9T|xk5+8jZ^u}#LNE%tn|_NREMXPsQ~@js zPLP}HOxfX{>rSX;L(C%-;%5HoQ~E!3n8l{O32>VHOG$E`mt#t`)<)`TZqv;$n4Xz+ zsLXtAKvx<^e6ga4Qid#fq3|bbYId{s0nvK8NqO2AZUWQG{7wd!WCx-C^l-0I8S))+ zL1(1V@DbHUV4)6oNJCG2E~B{7$^!VdGWUi)6D6!c=!brW@sa%`DIjx5_Xo0vB5NF^>AgR-44tA$5xpBC=SqLse^Dhufx<-@ z6aNVBC4J0VSTAzQHB=srS1ZxM@3b>Iy4#yc6C{Pb55Gp=f%&D3MjfKES|4D+{L)6# zHi=Ga2kF)sfYiS5WDEJ+89-8_ziVng0D%X3#L$5Cf@rHJ0uZ>gxz^Un*ReFakFWcO z4KdIUrI)bv@lqr1-uCo?t@;_j5^tvyS{*iXP@!CR;Ki`4p-wX$yAg?p6!px8FBluG z=xWNv#wB*+ClQsY>n%!L#WII? z_u7k$c<6mn0|xjm>W%Y1cKE*d_8J6z(RFzeNp}jzl7l3?WkLE$mKtfG=hFdHKXU9* z71oILA;o77<~X4l1OVqa=DMMN-;YFjFZ^&EaK9Zv2FQGqMWidR6pP1HS z{#AAlsS5guW);Sb2VtL+lZ#=V=EV_(y3mz|z+q=xm}YaNHRuX*Z4s;R^h9HctgCbj zF}_?H+#63ZM@k7mz4_lo$yZ|C)wl0Jmpk15J8S-znERiZK{o@9+Uf-gQ3U$G)8s!a z-Tyghm4$$)``?ePAkYwy|2>C;h2RIM95?fsoy>H*nc60dJB9~}tOvyj1b~u23JOX5 zf|is3P5VVI%p@r$Og?arrEis(}&(%Uu~n2RIFaDv$V0Yq1vgrxvF;6So`R5 zuNeQF{clDFgLsaQ_1|aeeeV4~v)7r|QlI_p-~6H?_b)_*I${9Uzjl0%^LpKYCjT$` zX?}0>os<)Rk8{-5S*hFKHkKP4=K*nF@+wB@!@K$5%csT>0{#nY2_x8WHJn`9m-<9H zq;dXbbilm>h8b?wVPQC4oM6qvUD0@Ii2#n4tNg7Z2_}wr$`p98b3wnEmj?>wt2s_C z3Ck&Lw3-}~Mh2B`&cxyePxdT;*&Pybmf43TF!EDnrZFgQNSr3fH~8|wsl30@#51Oj zDp9wc#i^K6F9WV9onqSC0>eCY4`TsNP|-U-7V}PYp)$@iz8@3rVwg#tc$#d17X3|% zbBgIq_0^qIZBi&2UJa{Dsc3aVKba*JXUX!`GH{gv;$oGReVM-2wo(_Mr?a}Ey{p~c zQEjo^P;zcczB-~=GO)qbtlR7Tnr1@N?IOC!Wq(%+ovogW$0*!*53YEFGAeW&fdVakZcVteh5?2cTO{?HFyj*Zf^H_Zcf}D zHol2=bqz-Wb?~zUZiE(~*VRGNQryLjad91;q~~VSRNKk3f_X+8OM!Ac;4qKEFeW`| z5#{<^1aXEb{$4On#vu)?dX&*rtwQ~9`Xl~M>;k66CVnt&aM^dVTpwe9^JdROeYjMV zav2^lI;AXOXKl#^@HHac!VaIr=OV#Jp2O-4v5Y~CtCri_0VQiMU4(_Vnnx{ zX4wBL(4e?6k0OQi6y?up(~h*bJ9F+IQaeD(c&?z6oxeDu@1El;DP+R$9BJoSLP>3u z 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

+ \ No newline at end of file From 27939b62007d7439f6df013a01a2842c12da4e7e Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 18:07:45 +0100 Subject: [PATCH 036/264] add QRCode to email, and start restructuring device-mapping-url-gen code --- .../tracking/DeviceMappingConstants.java | 64 +++++++++++++----- .../tracking/QRCodeURLCreationException.java | 9 +++ .../META-INF/MANIFEST.MF | 4 +- .../AddDeviceMappingToRaceLogDialog.java | 19 ++---- .../AddDeviceMappingToRegattaLogDialog.java | 26 ++++---- .../adminconsole/BaseQRIdentifierWidget.java | 8 +-- .../DeviceMappingQRCodeWidget.java | 12 +--- .../sailing/gwt/ui/client/SailingService.java | 5 +- .../gwt/ui/client/SailingServiceAsync.java | 3 +- .../gwt/ui/server/SailingServiceImpl.java | 52 +++++++++++++-- java/com.sap.sse.common/META-INF/MANIFEST.MF | 4 +- .../common/qrcode/QRCodeGenerationUtil.java | 65 +++++++++++++++++++ 12 files changed, 204 insertions(+), 67 deletions(-) create mode 100644 java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/QRCodeURLCreationException.java create mode 100644 java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java index affb0ce5572..7cb7ee30369 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java @@ -1,5 +1,8 @@ package com.sap.sailing.domain.common.racelog.tracking; +import com.google.gwt.http.client.URL; +import com.sap.sailing.domain.common.racelog.RaceLogServletConstants; + /** * Shared between GWT and Android. Used for creating and deciphering the URL encoded by the QRCode, which gives the * tracking app all necessary information for creating the device mapping of either the race or leaderboard/regatta. @@ -13,28 +16,57 @@ package com.sap.sailing.domain.common.racelog.tracking; * * @author Fredrik Teschke */ -public interface DeviceMappingConstants { +public class DeviceMappingConstants { // According to the HTTP protocol definition (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.3), only // schema and host are case insensitive. // But to adhere to best practices, and due to the deficiencies of some web servers and clients, // all URL components should be treated as case insensitive, preferring _underscores_ to CamelCase. - static final String URL_BASE = "/tracking/checkin"; - static final String URL_EVENT_ID = "event_id"; - static final String URL_LEADERBOARD_NAME = "leaderboard_name"; - static final String URL_COMPETITOR_ID_AS_STRING = "competitor_id"; - static final String URL_MARK_ID_AS_STRING = "mark_id"; - static final String URL_FROM_MILLIS = "from_millis"; - static final String URL_TO_MILLIS = "to_millis"; + public static final String URL_BASE = "/tracking/checkin"; + public static final String URL_EVENT_ID = "event_id"; + public static final String URL_LEADERBOARD_NAME = "leaderboard_name"; + public static final String URL_COMPETITOR_ID_AS_STRING = "competitor_id"; + public static final String URL_MARK_ID_AS_STRING = "mark_id"; + public static final String URL_FROM_MILLIS = "from_millis"; + public static final String URL_TO_MILLIS = "to_millis"; - static final String JSON_COMPETITOR_ID_AS_STRING = "competitorId"; - static final String JSON_MARK_ID_AS_STRING = "markId"; - static final String JSON_DEVICE_UUID = "deviceUuid"; - static final String JSON_DEVICE_TYPE = "deviceType"; - static final String JSON_PUSH_DEVICE_ID = "pushDeviceId"; - static final String JSON_FROM_MILLIS = "fromMillis"; - static final String JSON_TO_MILLIS = "toMillis"; - static final String JSON_TEAM_IMAGE_URI = "teamImageUri"; + public static final String JSON_COMPETITOR_ID_AS_STRING = "competitorId"; + public static final String JSON_MARK_ID_AS_STRING = "markId"; + public static final String JSON_DEVICE_UUID = "deviceUuid"; + public static final String JSON_DEVICE_TYPE = "deviceType"; + public static final String JSON_PUSH_DEVICE_ID = "pushDeviceId"; + public static final String JSON_FROM_MILLIS = "fromMillis"; + public static final String JSON_TO_MILLIS = "toMillis"; + public static final String JSON_TEAM_IMAGE_URI = "teamImageUri"; @Deprecated static final String APK_PATH = "/apps/com.sap.sailing.android.tracking.app.apk"; + + public static String encode(String value) { + //pathSegment instead of queryString, so that ' ' is encoded as '%20' + return URL.encodePathSegment(value); + } + + public static String getDeviceMappingForRegattaLogUrl(String serverUrlWithoutTrailingSlash, String eventId, + String leaderboardName, String mappedItemType, String mappedItemId) { + return serverUrlWithoutTrailingSlash + URL_BASE + "?" + + URL_EVENT_ID + "=" + encode(eventId) + "&" + + URL_LEADERBOARD_NAME + "=" + encode(leaderboardName) + + "&" + mappedItemType + "=" + encode(mappedItemId); + } + + @Deprecated + public static String getDeviceMappingForRaceLogUrl(String serverUrlWithoutTrailingSlash, String leaderboardName, + String raceColumnName, String fleetName, String mappedItemType, String mappedItemId, long fromMillis, + long toMillis) throws QRCodeURLCreationException { + if (fromMillis > toMillis) { + throw new QRCodeURLCreationException("from can't lie after to"); + } + return serverUrlWithoutTrailingSlash + DeviceMappingConstants.APK_PATH + "?" + + RaceLogServletConstants.PARAMS_LEADERBOARD_NAME + "=" + encode(leaderboardName) + "&" + + RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME + "=" + encode(raceColumnName) + + "&" + RaceLogServletConstants.PARAMS_RACE_FLEET_NAME + "=" + encode(fleetName) + + "&" + mappedItemType + "=" + encode(mappedItemId) + + "&" + DeviceMappingConstants.URL_FROM_MILLIS + "=" + fromMillis + + "&" + DeviceMappingConstants.URL_TO_MILLIS + "=" + toMillis; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/QRCodeURLCreationException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/QRCodeURLCreationException.java new file mode 100644 index 00000000000..aa2e0847644 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/QRCodeURLCreationException.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.common.racelog.tracking; + +public class QRCodeURLCreationException extends Exception { + private static final long serialVersionUID = -8243995470804772458L; + + public QRCodeURLCreationException(String message) { + super(message); + } +} diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 3fa3087b700..ca2ccde0022 100755 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -45,7 +45,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.operationaltransformation, com.sap.sse, com.sap.sse.security.common, - com.sap.sse.filestorage + com.sap.sse.filestorage, + org.apache.servicemix.bundles.zxing;bundle-version="3.1.0" Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy Import-Package: com.sap.sailing.polars, @@ -54,6 +55,7 @@ Import-Package: com.sap.sailing.polars, javax.jms;version="1.1.0", javax.mail;version="1.4.0", javax.mail.internet;version="1.4.0", + javax.mail.util;version="1.4.0", javax.servlet;version="3.1.0", javax.servlet.descriptor;version="3.1.0", javax.servlet.http;version="3.1.0", diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java index 2d14bf984cd..f005264b10d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java @@ -18,10 +18,9 @@ import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.racelog.RaceLogServletConstants; import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice; -import com.sap.sailing.gwt.ui.adminconsole.DeviceMappingQRCodeWidget.QRCodeURLCreationException; +import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException; import com.sap.sailing.gwt.ui.adminconsole.ItemToMapToDeviceSelectionPanel.SelectionChangedHandler; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -103,7 +102,7 @@ public class AddDeviceMappingToRaceLogDialog extends DataEntryDialog toMillis) { - throw new QRCodeURLCreationException("from cannt lie after to"); + throw new QRCodeURLCreationException("from can't lie after to"); } - return baseUrlWithoutTrailingSlash + DeviceMappingConstants.APK_PATH + "?" - + RaceLogServletConstants.PARAMS_LEADERBOARD_NAME + "=" + DeviceMappingQRCodeWidget.encode(leaderboardName) + "&" - + RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME + "=" + DeviceMappingQRCodeWidget.encode(raceColumnName) - + "&" + RaceLogServletConstants.PARAMS_RACE_FLEET_NAME + "=" + DeviceMappingQRCodeWidget.encode(fleetName) - + "&" + mappedItemQueryParam + "&" + DeviceMappingConstants.URL_FROM_MILLIS + "=" + fromMillis - + "&" + DeviceMappingConstants.URL_TO_MILLIS + "=" + toMillis; + return DeviceMappingConstants.getDeviceMappingForRaceLogUrl(baseUrlWithoutTrailingSlash, + leaderboardName, raceColumnName, fleetName, mappedItemType, mappedItemId, fromMillis, toMillis); } }); qrWidget.generateQRCode(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java index 44fcd58eaf1..93d3cfd2376 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java @@ -13,7 +13,7 @@ import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; -import com.sap.sailing.gwt.ui.adminconsole.DeviceMappingQRCodeWidget.QRCodeURLCreationException; +import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException; import com.sap.sailing.gwt.ui.adminconsole.ItemToMapToDeviceSelectionPanel.SelectionChangedHandler; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -35,11 +35,11 @@ public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog setupUi(); center(); } - + @Override protected void addMainContent(Panel mainPanel) { super.addMainContent(mainPanel); - + CaptionPanel inputPanel = new CaptionPanel(); Grid inputGrid = new Grid(1, 2); inputPanel.add(inputGrid); @@ -62,13 +62,13 @@ public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog public void onSuccess(Collection result) { events.addItems(result); } - + @Override public void onFailure(Throwable caught) { errorReporter.reportError("Could not load events: " + caught.getMessage()); } }); - + itemSelectionPanel = new ItemToMapToDeviceSelectionPanel(sailingService, stringMessages, errorReporter, new SelectionChangedHandler() { @Override @@ -85,33 +85,31 @@ public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog } }, null); - //load table content + // load table content sailingService.getCompetitorRegistrations(leaderboardName, itemSelectionPanel.getSetCompetitorsCallback()); - //TODO marks from RegattaLog also? + // TODO marks from RegattaLog also? qrWidget = new DeviceMappingQRCodeWidget(stringMessages, new DeviceMappingQRCodeWidget.URLFactory() { @Override - public String createURL(String baseUrlWithoutTrailingSlash, String mappedItemQueryParam) + public String createURL(String baseUrlWithoutTrailingSlash, String mappedItemType, String mappedItemId) throws QRCodeURLCreationException { if (events.getValue() == null) { throw new QRCodeURLCreationException("no event selected"); } String eventIdAsString = events.getValue().id.toString(); - return baseUrlWithoutTrailingSlash + DeviceMappingConstants.URL_BASE - + "?" + DeviceMappingConstants.URL_EVENT_ID + "=" + DeviceMappingQRCodeWidget.encode(eventIdAsString) - + "&" + DeviceMappingConstants.URL_LEADERBOARD_NAME + "=" + DeviceMappingQRCodeWidget.encode(leaderboardName) - + "&" + mappedItemQueryParam; + return DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(baseUrlWithoutTrailingSlash, eventIdAsString, + leaderboardName, mappedItemType, mappedItemId); } }); qrWidget.generateQRCode(); - + VerticalPanel leftSidePanel = new VerticalPanel(); leftSidePanel.add(inputPanel); CaptionPanel qrContentPanel = new CaptionPanel(); leftSidePanel.add(qrContentPanel); qrContentPanel.add(qrWidget); - + HorizontalPanel panel = new HorizontalPanel(); mainPanel.add(panel); panel.add(leftSidePanel); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BaseQRIdentifierWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BaseQRIdentifierWidget.java index 143472a1efe..8f73b604e63 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BaseQRIdentifierWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/BaseQRIdentifierWidget.java @@ -4,7 +4,6 @@ import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.http.client.URL; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HasHorizontalAlignment; @@ -13,7 +12,7 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.ui.adminconsole.DeviceMappingQRCodeWidget.QRCodeURLCreationException; +import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.QRCodeComposite; @@ -91,9 +90,4 @@ public abstract class BaseQRIdentifierWidget implements IsWidget { protected void setError(String text) { error.setText(text); } - - public static String encode(String value) { - //pathSegment instead of queryString, so that ' ' is encoded as '%20' - return URL.encodePathSegment(value); - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingQRCodeWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingQRCodeWidget.java index 4cdc985eac3..20815e78d99 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingQRCodeWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingQRCodeWidget.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.adminconsole; import com.google.gwt.http.client.URL; import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; +import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.QRCodeComposite; @@ -13,15 +14,8 @@ public class DeviceMappingQRCodeWidget extends BaseQRIdentifierWidget { private String mappedItemId; private String mappedItemType; - static class QRCodeURLCreationException extends Exception { - private static final long serialVersionUID = -8243995470804772458L; - public QRCodeURLCreationException(String message) { - super(message); - } - } - static interface URLFactory { - String createURL(String baseUrlWithoutTrailingSlash, String mappedItemQueryParam) + String createURL(String baseUrlWithoutTrailingSlash, String mappedItemType, String mappedItemId) throws QRCodeURLCreationException; } @@ -52,6 +46,6 @@ public class DeviceMappingQRCodeWidget extends BaseQRIdentifierWidget { throw new QRCodeURLCreationException("No item selected for mapping"); } - return urlFactory.createURL(serverUrl, mappedItemType + "=" + encode(mappedItemId)); + return urlFactory.createURL(serverUrl, mappedItemType, mappedItemId); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 252d42fec31..b95b28cbf81 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -88,8 +88,8 @@ import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.search.KeywordQuery; -import com.sap.sse.security.shared.MailException; import com.sap.sse.gwt.client.filestorage.FileStorageManagementGwtService; +import com.sap.sse.security.shared.MailException; /** * The client side stub for the RPC service. Usually, when a null date is passed to @@ -577,5 +577,6 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS List getDeviceMappingsFromLogHierarchy(String leaderboardName, String raceColumnName, String fleetName) throws TransformationException; - void sendInvitationEmailToCompetitors(Iterable competitors) throws MailException; + void inviteCompetitorsViaEmail(String serverUrlWithoutTrailingSlash, String eventId, String leaderboardName) + throws MailException; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index fc083d18b0a..b72971078cd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -699,6 +699,7 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM void getActiveFileStorageServiceName(AsyncCallback callback); - void sendInvitationEmailToCompetitors(Iterable competitors, AsyncCallback callback); + void inviteCompetitorsViaEmail(String serverUrlWithoutTrailingSlash, String eventId, String leaderboardName, + AsyncCallback callback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 01ca6f185fa..c28bd6168b5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -49,13 +49,19 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.mail.BodyPart; import javax.mail.MessagingException; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage.RecipientType; +import javax.mail.internet.MimeMultipart; +import javax.mail.util.ByteArrayDataSource; import javax.servlet.ServletContext; import org.apache.http.client.ClientProtocolException; @@ -65,6 +71,7 @@ import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; import com.google.gwt.user.client.Window; +import com.google.zxing.WriterException; import com.sap.sailing.domain.abstractlog.AbstractLog; import com.sap.sailing.domain.abstractlog.MultiLogAnalyzer; import com.sap.sailing.domain.abstractlog.Revokable; @@ -201,6 +208,7 @@ import com.sap.sailing.domain.common.racelog.FlagPole; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice; import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; @@ -428,6 +436,7 @@ import com.sap.sse.common.Util.Triple; import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.TimeRangeImpl; +import com.sap.sse.common.qrcode.QRCodeGenerationUtil; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; import com.sap.sse.filestorage.FileStorageService; @@ -5524,15 +5533,19 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public void sendInvitationEmailToCompetitors(Iterable competitors) throws MailException { + public void inviteCompetitorsViaEmail(String serverUrlWithoutTrailingSlash, String eventId, + String leaderboardName) throws MailException { if (!(this.mailProperties != null && this.mailProperties.containsKey("mail.transport.protocol"))) { Window.alert("Check this, not implemented yet!"); return; } + //TODO check bug 2587 + Iterable competitors = getService().getLeaderboardByName(leaderboardName).getAllCompetitors(); + StringBuilder occuredExceptions = new StringBuilder(); - for (CompetitorDTO competitor : competitors){ + for (Competitor competitor : competitors){ final String toAddress = competitor.getEmail(); if (toAddress != null) { Session session = Session.getInstance(this.mailProperties, new SMTPAuthenticator()); @@ -5540,14 +5553,45 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S try { msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); msg.setSubject("Invitation to a SAP Sailing Analytics event"); //FIXME: I18N? - msg.setContent("Test", "text/plain"); msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); + + // taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm + // This mail has 2 part, the BODY and the embedded image + MimeMultipart multipart = new MimeMultipart("related"); + + // first part (the html) + BodyPart messageBodyPart = new MimeBodyPart(); + String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash, + eventId, leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING, + competitor.getId().toString()); + String htmlText = String.format("

Welcome to %s

" + + "

Scan the following QRCode or visit the URL using your smartphone to register as %s

" + + "" + + "
%s", leaderboardName, competitor.getName(), url, url); + messageBodyPart.setContent(htmlText, "text/html"); + // add it + multipart.addBodyPart(messageBodyPart); + + // second part (the image) + messageBodyPart = new MimeBodyPart(); + InputStream imageIs = QRCodeGenerationUtil.create(url, 64); + DataSource imageDs = new ByteArrayDataSource(imageIs, "image/png"); + + messageBodyPart.setDataHandler(new DataHandler(imageDs)); + messageBodyPart.setHeader("Content-ID", ""); + + // add image to the multipart + multipart.addBodyPart(messageBodyPart); + + // put everything together + msg.setContent(multipart); + Transport ts = session.getTransport(); ts.connect(); ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); ts.close(); logger.info("mail sent to competitor " + competitor.getName() + " with e-mail address " + toAddress); - } catch (MessagingException e) { + } catch (MessagingException | WriterException | IOException e) { logger.log(Level.SEVERE, "Error trying to send mail to user " + competitor.getName() + " with e-mail address " + toAddress, e); occuredExceptions.append(e.getMessage()+"\r\n"); diff --git a/java/com.sap.sse.common/META-INF/MANIFEST.MF b/java/com.sap.sse.common/META-INF/MANIFEST.MF index 56290d7ee65..766961d8172 100644 --- a/java/com.sap.sse.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.common/META-INF/MANIFEST.MF @@ -8,7 +8,9 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Export-Package: com.sap.sse.common, com.sap.sse.common.filter, com.sap.sse.common.impl, + com.sap.sse.common.qrcode, com.sap.sse.common.scalablevalue, com.sap.sse.common.search -Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0" +Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0", + org.apache.servicemix.bundles.zxing;bundle-version="3.1.0" Bundle-ActivationPolicy: lazy diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java b/java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java new file mode 100644 index 00000000000..9c0dce56450 --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java @@ -0,0 +1,65 @@ +package com.sap.sse.common.qrcode; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; + +import javax.imageio.ImageIO; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +/** + * QRCode generator based on the ZXing library. + * + * TODO have a look at repackaging QRGen as OSGi bundle. + * @author Fredrik Teschke + * + */ +public class QRCodeGenerationUtil { + public static InputStream create(String text, int sizeInPixels) throws WriterException, IOException { + Hashtable hintMap = new Hashtable(); + hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); + return create(text, sizeInPixels, hintMap); + } + + public static InputStream create(String text, int sizeInPixels, + Hashtable hintMap) throws WriterException, IOException { + //taken from http://crunchify.com/java-simple-qr-code-generator-example/ + + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix byteMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, sizeInPixels, sizeInPixels, hintMap); + int matrixSize = byteMatrix.getWidth(); + BufferedImage image = new BufferedImage(matrixSize, matrixSize, BufferedImage.TYPE_INT_RGB); + image.createGraphics(); + + Graphics2D graphics = (Graphics2D) image.getGraphics(); + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, matrixSize, matrixSize); + graphics.setColor(Color.BLACK); + + for (int i = 0; i < matrixSize; i++) { + for (int j = 0; j < matrixSize; j++) { + if (byteMatrix.get(i, j)) { + graphics.fillRect(i, j, 1, 1); + } + } + } + + //TODO create InputStream from streamed output of image instead of handling complete byte arrays + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ImageIO.write(image, "gif", os); + InputStream is = new ByteArrayInputStream(os.toByteArray()); + return is; + } +} From ce87cf97ca29a81ab6bcfcc79afb24539f47e362 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 9 Feb 2015 18:10:36 +0100 Subject: [PATCH 037/264] removed warSourceDirectory from pom.xml --- java/com.sap.sse.gwt.theme/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/java/com.sap.sse.gwt.theme/pom.xml b/java/com.sap.sse.gwt.theme/pom.xml index b426d91ca1a..3770ea11724 100644 --- a/java/com.sap.sse.gwt.theme/pom.xml +++ b/java/com.sap.sse.gwt.theme/pom.xml @@ -67,8 +67,6 @@ ${basedir}/.generated 7 ${basedir} - ${basedir}/src/main/resources - compile From daa3c680513ea0fb25318735298f3b986745d037 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 9 Feb 2015 18:10:59 +0100 Subject: [PATCH 038/264] added theme project to main pom.xml --- java/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/java/pom.xml b/java/pom.xml index 94d1f27c721..ffb0963fe6a 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -36,6 +36,7 @@ com.sap.sse.common.test com.sap.sse.filestorage com.sap.sse.filestorage.test + com.sap.sse.gwt.theme com.sap.sailing.domain.common com.sap.sailing.domain.shared.android com.sap.sailing.domain From e004c433ccd39e7315fcc16bf611f2bc3e63a1d4 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 21:03:50 +0100 Subject: [PATCH 039/264] pull out mail functionality into own service and bundle - needs to live outside com.sap.sse due to dependency on replication - use mail.properties instead of security.properties - introduced ServiceTrackerFactory, which led to refactorings in SailingServiceImpl and ~Mock --- configuration/buildAndUpdateProduct.sh | 6 +- .../raceanalysis.product | 1 + java/com.sap.sailing.feature/feature.xml | 7 + .../gwt/ui/test/SailingServiceImplMock.java | 134 ++++------- .../gwt/ui/server/SailingServiceImpl.java | 143 +++--------- java/com.sap.sse.mail/.classpath | 7 + java/com.sap.sse.mail/.project | 28 +++ .../.settings/org.eclipse.jdt.core.prefs | 7 + .../.settings/org.eclipse.pde.core.prefs | 3 + java/com.sap.sse.mail/META-INF/MANIFEST.MF | 15 ++ java/com.sap.sse.mail/build.properties | 4 + java/com.sap.sse.mail/pom.xml | 12 + .../src/com/sap/sse/mail}/MailException.java | 2 +- .../src/com/sap/sse/mail/MailOperation.java | 15 ++ .../src/com/sap/sse/mail/MailService.java | 30 +++ .../src/com/sap/sse/mail/impl/Activator.java | 56 +++++ .../sap/sse/mail/impl/MailServiceImpl.java | 209 ++++++++++++++++++ .../sse/mail/impl/ReplicableMailService.java | 12 + .../test/AbstractSecurityReplicationTest.java | 5 +- .../test/SimpleSecurityReplicationTest.java | 2 +- .../META-INF/MANIFEST.MF | 4 +- .../com/sap/sse/security/test/LoginTest.java | 3 +- .../META-INF/MANIFEST.MF | 3 +- .../ui/client/UserManagementService.java | 2 +- .../ui/server/UserManagementServiceImpl.java | 2 +- .../com.sap.sse.security/META-INF/MANIFEST.MF | 6 +- .../com/sap/sse/security/SecurityService.java | 2 +- .../com/sap/sse/security/impl/Activator.java | 28 +-- .../security/impl/SecurityServiceImpl.java | 66 ++---- .../sap/sse/util/ServiceTrackerFactory.java | 18 ++ java/pom.xml | 1 + .../{security.properties => mail.properties} | 0 java/target/env.sh | 2 +- 33 files changed, 550 insertions(+), 285 deletions(-) create mode 100755 java/com.sap.sse.mail/.classpath create mode 100755 java/com.sap.sse.mail/.project create mode 100755 java/com.sap.sse.mail/.settings/org.eclipse.jdt.core.prefs create mode 100755 java/com.sap.sse.mail/.settings/org.eclipse.pde.core.prefs create mode 100755 java/com.sap.sse.mail/META-INF/MANIFEST.MF create mode 100755 java/com.sap.sse.mail/build.properties create mode 100644 java/com.sap.sse.mail/pom.xml rename java/{com.sap.sse.security.common/src/com/sap/sse/security/shared => com.sap.sse.mail/src/com/sap/sse/mail}/MailException.java (87%) create mode 100644 java/com.sap.sse.mail/src/com/sap/sse/mail/MailOperation.java create mode 100644 java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java create mode 100644 java/com.sap.sse.mail/src/com/sap/sse/mail/impl/Activator.java create mode 100644 java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java create mode 100644 java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java create mode 100644 java/com.sap.sse/src/com/sap/sse/util/ServiceTrackerFactory.java rename java/target/configuration/{security.properties => mail.properties} (100%) diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index 4b4f9137874..efbc8ad4001 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -249,7 +249,7 @@ if [[ "$@" == "release" ]]; then cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-deployer.xml configuration/jetty/etc cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/realm.properties configuration/jetty/etc cp -v $PROJECT_HOME/java/target/configuration/monitoring.properties configuration/ - cp -v $PROJECT_HOME/java/target/configuration/security.properties configuration/ + cp -v $PROJECT_HOME/java/target/configuration/mail.properties configuration/ cp -v $PROJECT_HOME/configuration/mongodb.cfg $ACDIR/ cp -v $PROJECT_HOME/java/target/udpmirror $ACDIR/ cp -v $PROJECT_HOME/java/target/http2udpmirror $ACDIR @@ -718,7 +718,7 @@ if [[ "$@" == "install" ]] || [[ "$@" == "all" ]]; then if [ ! -f "$ACDIR/env.sh" ]; then cp -v $PROJECT_HOME/java/target/env.sh $ACDIR/ cp -v $PROJECT_HOME/java/target/configuration/monitoring.properties $ACDIR/configuration/ - cp -v $PROJECT_HOME/java/target/configuration/security.properties $ACDIR/configuration/ + cp -v $PROJECT_HOME/java/target/configuration/mail.properties $ACDIR/configuration/ cp -v $PROJECT_HOME/java/target/configuration/logging.properties $ACDIR/configuration/ fi @@ -799,7 +799,7 @@ if [[ "$@" == "remote-deploy" ]]; then $SCP_CMD $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-deployer.xml $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/jetty/etc $SCP_CMD $PROJECT_HOME/java/target/configuration/jetty/etc/realm.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/jetty/etc $SCP_CMD $PROJECT_HOME/java/target/configuration/monitoring.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/ - $SCP_CMD $PROJECT_HOME/java/target/configuration/security.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/ + $SCP_CMD $PROJECT_HOME/java/target/configuration/mail.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/ $SCP_CMD $PROJECT_HOME/java/target/env.sh $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/ $SCP_CMD $PROJECT_HOME/java/target/start $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/ diff --git a/java/com.sap.sailing.feature.p2build/raceanalysis.product b/java/com.sap.sailing.feature.p2build/raceanalysis.product index 71b6e30fe1b..6f0f4447caf 100755 --- a/java/com.sap.sailing.feature.p2build/raceanalysis.product +++ b/java/com.sap.sailing.feature.p2build/raceanalysis.product @@ -58,6 +58,7 @@ + diff --git a/java/com.sap.sailing.feature/feature.xml b/java/com.sap.sailing.feature/feature.xml index 543e8a8950d..c83bd9b70f2 100755 --- a/java/com.sap.sailing.feature/feature.xml +++ b/java/com.sap.sailing.feature/feature.xml @@ -409,4 +409,11 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al version="0.0.0" unpack="false"/> + + diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java index 31bba094c52..7803176ca7c 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java @@ -1,8 +1,5 @@ package com.sap.sailing.gwt.ui.test; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import java.io.IOException; import java.net.UnknownHostException; import java.text.ParseException; @@ -11,9 +8,6 @@ import java.util.List; import javax.xml.bind.JAXBException; -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; - import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.common.RegattaIdentifier; @@ -27,7 +21,6 @@ import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory; import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayService; -import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayServiceFactory; import com.sap.sailing.domain.tracking.RaceHandle; import com.sap.sailing.domain.tracking.TrackerManager; import com.sap.sailing.domain.tractracadapter.TracTracAdapterFactory; @@ -43,72 +36,37 @@ import com.sap.sse.replication.impl.ReplicationServiceImpl; public class SailingServiceImplMock extends SailingServiceImpl { private static final long serialVersionUID = 8564037671550730455L; - private RacingEventService service; - + + private final RacingEventService service; + private ReplicationService replicationService; + private final ScoreCorrectionProvider scoreCorrectionProvider = null; + private final TracTracAdapterFactory tracTracAdapterFactory = TracTracAdapterFactory.INSTANCE; + private final SwissTimingAdapterFactory swissTimingAdapterFactory; + private final SwissTimingReplayService swissTimingReplayService = null; + private final RaceLogTrackingAdapterFactory raceLogTrackingAdapterFactory = RaceLogTrackingAdapterFactory.INSTANCE; + public SailingServiceImplMock() { super(); service = new RacingEventServiceImpl(true, new MockSmartphoneImeiServiceFinderFactory()); - } - - @Override - protected ServiceTracker createAndOpenRacingEventServiceTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - when(result.getService()).thenReturn(new RacingEventServiceImpl()); - return result; - } - - @Override - protected ServiceTracker createAndOpenReplicationServiceTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); try { - final RacingEventService replicable = new RacingEventServiceImpl(); - final ReplicationServiceImpl replicationService = - new ReplicationServiceImpl("test exchange", "localhost", 0, - new ReplicationInstancesManager(), new AbstractReplicablesProvider() { - @Override - public Iterable> getReplicables() { - return Collections.>singleton(replicable); - } - }); - when(result.getService()).thenReturn(replicationService); + replicationService = new ReplicationServiceImpl("test exchange", "localhost", 0, + new ReplicationInstancesManager(), new AbstractReplicablesProvider() { + @Override + public Iterable> getReplicables() { + return Collections.> singleton(service); + } + }); } catch (IOException e) { - throw new RuntimeException(); + e.printStackTrace(); } - return result; - } - - @Override - protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( - BundleContext bundleContext) { - return null; - } - - @Override - protected ServiceTracker createAndOpenTracTracAdapterTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - when(result.getService()).thenReturn(TracTracAdapterFactory.INSTANCE); - return result; - } - - @Override - protected ServiceTracker createAndOpenSwissTimingAdapterTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - when(result.getService()).thenReturn(new SwissTimingAdapterFactory() { + swissTimingAdapterFactory = new SwissTimingAdapterFactory() { @Override public SwissTimingAdapter getOrCreateSwissTimingAdapter(DomainFactory baseDomainFactory) { return new SwissTimingAdapter() { @Override public List getSwissTimingRaceRecords( - String hostname, int port) throws InterruptedException, - UnknownHostException, IOException, ParseException { + String hostname, int port) throws InterruptedException, UnknownHostException, IOException, + ParseException { // TODO Auto-generated method stub return null; } @@ -148,45 +106,43 @@ public class SailingServiceImplMock extends SailingServiceImpl { } }; } - }); - return result; + }; + } + + public ScoreCorrectionProvider getScoreCorrectionProvider() { + return scoreCorrectionProvider; } @Override - protected ServiceTracker createAndOpenSwissTimingReplayServiceTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - final SwissTimingReplayServiceFactory swissTimingReplayService = new SwissTimingReplayServiceFactory() { - @Override - public SwissTimingReplayService createSwissTimingReplayService( - com.sap.sailing.domain.swisstimingadapter.DomainFactory domainFactory) { - // TODO Auto-generated method stub - return null; - } - }; - when(result.getService()).thenReturn(swissTimingReplayService); - return result; + protected TracTracAdapterFactory getTracTracAdapterFactory() { + return tracTracAdapterFactory; + } + + @Override + protected SwissTimingAdapterFactory getSwissTimingAdapterFactory() { + return swissTimingAdapterFactory; + } + + @Override + protected SwissTimingReplayService getSwissTimingReplayService() { + return swissTimingReplayService; + } + + @Override + protected RaceLogTrackingAdapterFactory getRaceLogTrackingAdapterFactory() { + return raceLogTrackingAdapterFactory; } @Override - protected ServiceTracker createAndOpenRaceLogTrackingAdapterTracker( - BundleContext context) { - @SuppressWarnings("unchecked") - ServiceTracker result = mock(ServiceTracker.class); - RaceLogTrackingAdapterFactory factory = RaceLogTrackingAdapterFactory.INSTANCE; - when(result.getService()).thenReturn(factory); - return result; + protected ReplicationService getReplicationService() { + return replicationService; } @Override protected RacingEventService getService() { - if (service == null) { - service = new RacingEventServiceImpl(); - } return service; } - + public RacingEventService getRacingEventService() { return getService(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 8ad3755d8cd..5e50344d0d5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -429,6 +429,7 @@ import com.sap.sse.replication.ReplicationFactory; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.ReplicationService; import com.sap.sse.replication.impl.ReplicaDescriptor; +import com.sap.sse.util.ServiceTrackerFactory; import com.sapsailing.xrr.structureimport.eventimport.RegattaJSON; /** @@ -501,21 +502,25 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S activator.setSailingService(this); // register so this service is informed when the bundle shuts down } quickRanksLiveCache = new QuickRanksLiveCache(this); - racingEventServiceTracker = createAndOpenRacingEventServiceTracker(context); - replicationServiceTracker = createAndOpenReplicationServiceTracker(context); - resultUrlRegistryServiceTracker = createAndOpenResultUrlRegistryServiceTracker(context); - swissTimingAdapterTracker = createAndOpenSwissTimingAdapterTracker(context); - tractracAdapterTracker = createAndOpenTracTracAdapterTracker(context); - raceLogTrackingAdapterTracker = createAndOpenRaceLogTrackingAdapterTracker(context); - deviceIdentifierStringSerializationHandlerTracker = createAndOpenDeviceIdentifierStringSerializationHandlerTracker(context); - igtimiAdapterTracker = createAndOpenIgtimiTracker(context); + racingEventServiceTracker = ServiceTrackerFactory.createAndOpen(context, RacingEventService.class); + replicationServiceTracker = ServiceTrackerFactory.createAndOpen(context, ReplicationService.class); + resultUrlRegistryServiceTracker = ServiceTrackerFactory.createAndOpen(context, ResultUrlRegistry.class); + swissTimingAdapterTracker = ServiceTrackerFactory.createAndOpen(context, SwissTimingAdapterFactory.class); + tractracAdapterTracker = ServiceTrackerFactory.createAndOpen(context, TracTracAdapterFactory.class); + raceLogTrackingAdapterTracker = ServiceTrackerFactory.createAndOpen(context, + RaceLogTrackingAdapterFactory.class); + deviceIdentifierStringSerializationHandlerTracker = ServiceTrackerFactory.createAndOpen(context, + DeviceIdentifierStringSerializationHandler.class); + igtimiAdapterTracker = ServiceTrackerFactory.createAndOpen(context, IgtimiConnectionFactory.class); baseDomainFactory = getService().getBaseDomainFactory(); mongoObjectFactory = getService().getMongoObjectFactory(); domainObjectFactory = getService().getDomainObjectFactory(); // TODO what about passing on the mongo/domain object factory to obtain an according SwissTimingAdapterPersistence instance similar to how the tractracDomainObjectFactory etc. are created below? swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE; - swissTimingReplayService = getSwissTimingReplayService(context); - scoreCorrectionProviderServiceTracker = createAndOpenScoreCorrectionProviderServiceTracker(context); + swissTimingReplayService = ServiceTrackerFactory.createAndOpen(context, SwissTimingReplayServiceFactory.class) + .getService().createSwissTimingReplayService(getSwissTimingAdapter().getSwissTimingDomainFactory()); + scoreCorrectionProviderServiceTracker = ServiceTrackerFactory.createAndOpen(context, + ScoreCorrectionProvider.class); tractracDomainObjectFactory = com.sap.sailing.domain.tractracadapter.persistence.PersistenceFactory.INSTANCE .createDomainObjectFactory(mongoObjectFactory.getDatabase(), getTracTracAdapter() .getTracTracDomainFactory()); @@ -554,89 +559,26 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S quickRanksLiveCache.stop(); } - protected SwissTimingReplayService getSwissTimingReplayService(BundleContext context) { - return createAndOpenSwissTimingReplayServiceTracker(context).getService().createSwissTimingReplayService(getSwissTimingAdapter().getSwissTimingDomainFactory()); + protected SwissTimingAdapterFactory getSwissTimingAdapterFactory() { + return swissTimingAdapterTracker.getService(); } protected SwissTimingAdapter getSwissTimingAdapter() { - return swissTimingAdapterTracker.getService().getOrCreateSwissTimingAdapter(baseDomainFactory); + return getSwissTimingAdapterFactory().getOrCreateSwissTimingAdapter(baseDomainFactory); + } + + protected TracTracAdapterFactory getTracTracAdapterFactory() { + return tractracAdapterTracker.getService(); } protected TracTracAdapter getTracTracAdapter() { - return tractracAdapterTracker.getService().getOrCreateTracTracAdapter(baseDomainFactory); - } - - protected ServiceTracker createAndOpenTracTracAdapterTracker(BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, TracTracAdapterFactory.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenIgtimiTracker(BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, IgtimiConnectionFactory.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenSwissTimingAdapterTracker( - BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, SwissTimingAdapterFactory.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenSwissTimingReplayServiceTracker( - BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, SwissTimingReplayServiceFactory.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenRaceLogTrackingAdapterTracker( - BundleContext context) { - ServiceTracker result = - new ServiceTracker( - context, RaceLogTrackingAdapterFactory.class.getName(), null); - result.open(); - return result; + return getTracTracAdapterFactory().getOrCreateTracTracAdapter(baseDomainFactory); } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); } - protected ServiceTracker createAndOpenRacingEventServiceTracker( - BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, RacingEventService.class.getName(), null); - result.open(); - return result; - } - - protected ServiceTracker createAndOpenResultUrlRegistryServiceTracker( - BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, ResultUrlRegistry.class.getName(), null); - result.open(); - return result; - } - - /** - * Asks the OSGi system for registered score correction provider services - */ - protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( - BundleContext bundleContext) { - ServiceTracker tracker = new ServiceTracker(bundleContext, - ScoreCorrectionProvider.class.getName(), - /* customizer */null); - tracker.open(); - return tracker; - } - @Override public Iterable getScoreCorrectionProviderNames() { List result = new ArrayList(); @@ -683,15 +625,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return new ScoreCorrectionProviderDTO(scoreCorrectionProvider.getName(), hasResultsForBoatClassFromDateByEventName); } - protected ServiceTracker createAndOpenReplicationServiceTracker( - BundleContext context) { - ServiceTracker result = - new ServiceTracker( - context, ReplicationService.class.getName(), null); - result.open(); - return result; - } - /** * If date is null, the {@link LiveLeaderboardUpdater} for the * leaderboardName requested is obtained or created if it doesn't exist yet. The request is then passed @@ -1114,10 +1047,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } } - private SwissTimingReplayService getSwissTimingReplayService() { - return swissTimingReplayService; - } - @Override public List getPreviousTracTracConfigurations() throws Exception { Iterable configs = tractracDomainObjectFactory.getTracTracConfigurations(); @@ -2237,7 +2166,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return racingEventServiceTracker.getService(); // grab the service } - private ReplicationService getReplicationService() { + protected ReplicationService getReplicationService() { return replicationServiceTracker.getService(); } @@ -2698,6 +2627,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } } } + + protected SwissTimingReplayService getSwissTimingReplayService() { + return swissTimingReplayService; + } @Override public List listSwissTiminigReplayRaces(String swissTimingUrl) { @@ -4735,9 +4668,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private IgtimiConnectionFactory getIgtimiConnectionFactory() { return igtimiAdapterTracker.getService(); } + + protected RaceLogTrackingAdapterFactory getRaceLogTrackingAdapterFactory() { + return raceLogTrackingAdapterTracker.getService(); + } protected RaceLogTrackingAdapter getRaceLogTrackingAdapter() { - return raceLogTrackingAdapterTracker.getService().getAdapter(getBaseDomainFactory()); + return getRaceLogTrackingAdapterFactory().getAdapter(getBaseDomainFactory()); } @Override @@ -5056,7 +4993,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S getRaceLogTrackingAdapter().copyCourseAndCompetitors(fromRaceLog, toRaceLogs, baseDomainFactory, getService()); } - private TypeBasedServiceFinder getDeviceIdentifierStringSerializerHandlerFinder( + protected TypeBasedServiceFinder getDeviceIdentifierStringSerializerHandlerFinder( boolean withFallback) { TypeBasedServiceFinderFactory factory = getService().getTypeBasedServiceFinderFactory(); TypeBasedServiceFinder finder = factory.createServiceFinder(DeviceIdentifierStringSerializationHandler.class); @@ -5179,16 +5116,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceLog.add(event); } - private ServiceTracker - createAndOpenDeviceIdentifierStringSerializationHandlerTracker(BundleContext context) { - ServiceTracker tracker = - new ServiceTracker( - context, DeviceIdentifierStringSerializationHandler.class, null); - - tracker.open(); - return tracker; - } - @Override public List getDeserializableDeviceIdentifierTypes() { List result = new ArrayList(); @@ -5429,7 +5356,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return data; } - private FileStorageService getFileStorageService(String name) { + protected FileStorageService getFileStorageService(String name) { if (name == null || name.equals("")) { return null; } diff --git a/java/com.sap.sse.mail/.classpath b/java/com.sap.sse.mail/.classpath new file mode 100755 index 00000000000..b862a296d38 --- /dev/null +++ b/java/com.sap.sse.mail/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sse.mail/.project b/java/com.sap.sse.mail/.project new file mode 100755 index 00000000000..05362453cda --- /dev/null +++ b/java/com.sap.sse.mail/.project @@ -0,0 +1,28 @@ + + + com.sap.sse.mail + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sse.mail/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sse.mail/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..295926d9641 --- /dev/null +++ b/java/com.sap.sse.mail/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sse.mail/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sse.mail/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..b7e72d019c2 --- /dev/null +++ b/java/com.sap.sse.mail/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sse.mail/META-INF/MANIFEST.MF b/java/com.sap.sse.mail/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..2402b0d40ef --- /dev/null +++ b/java/com.sap.sse.mail/META-INF/MANIFEST.MF @@ -0,0 +1,15 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Sse Email +Bundle-SymbolicName: com.sap.sse.mail +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Export-Package: com.sap.sse.mail +Import-Package: javax.mail, + javax.mail.internet, + org.osgi.framework +Require-Bundle: com.sap.sse.replication, + com.sap.sse.operationaltransformation, + com.sap.sse.common, + com.sap.sse diff --git a/java/com.sap.sse.mail/build.properties b/java/com.sap.sse.mail/build.properties new file mode 100755 index 00000000000..41eb6ade2b4 --- /dev/null +++ b/java/com.sap.sse.mail/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sse.mail/pom.xml b/java/com.sap.sse.mail/pom.xml new file mode 100644 index 00000000000..5d7f50f4093 --- /dev/null +++ b/java/com.sap.sse.mail/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sse.mail + eclipse-plugin + diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/MailException.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailException.java similarity index 87% rename from java/com.sap.sse.security.common/src/com/sap/sse/security/shared/MailException.java rename to java/com.sap.sse.mail/src/com/sap/sse/mail/MailException.java index 9d3491ee00b..79013c8aebb 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/MailException.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailException.java @@ -1,4 +1,4 @@ -package com.sap.sse.security.shared; +package com.sap.sse.mail; import java.io.Serializable; diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailOperation.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailOperation.java new file mode 100644 index 00000000000..908e751563a --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailOperation.java @@ -0,0 +1,15 @@ +package com.sap.sse.mail; + +import com.sap.sse.mail.impl.ReplicableMailService; +import com.sap.sse.replication.OperationWithResult; + +public interface MailOperation extends OperationWithResult { + /** + * By default, the mail-related operations will perform an update on the receiving replica that in turn + * will trigger replication of this change transitively. + */ + default boolean isRequiresExplicitTransitiveReplication() { + return false; + } + +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java new file mode 100644 index 00000000000..810f473c0c1 --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java @@ -0,0 +1,30 @@ +package com.sap.sse.mail; + +import javax.mail.Multipart; + +import com.sap.sse.mail.impl.ReplicableMailService; +import com.sap.sse.replication.Replicable; +import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; + + +/** + * Announced via OSGi service registry. Requires {@code mail.properties} file in {@code configuration} directory + * to be correctly configured. + * + * A mail should only be sent out once by one instance. This is similar to only adding an item once to a store + * (database). Therefore - while a sendMail() operation is replicated - it is only executed by one instance, + * for now the master instance. All replicas receive an empty ({@code null}) mail configuration during + * {@link Replicable#initiallyFillFrom initial replication}, and therefore do not try to send mails themselves. + * + * @author Axel Uhl + * @author Fredrik Teschke + * + */ +public interface MailService extends ReplicableWithObjectInputStream> { + void sendMail(String toAddress, String subject, String body) throws MailException; + + /** + * Send mail with multipart content (e.g. inline image). + */ + void sendMail(String toAddress, String subject, Multipart multipartContent) throws MailException; +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/Activator.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/Activator.java new file mode 100644 index 00000000000..45b6b3e46ec --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/Activator.java @@ -0,0 +1,56 @@ +package com.sap.sse.mail.impl; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +import com.sap.sse.mail.MailService; +import com.sap.sse.replication.Replicable; +import com.sap.sse.util.ClearStateTestSupport; + +public class Activator implements BundleActivator { + private static final Logger logger = Logger.getLogger(Activator.class.getName()); + + private ServiceRegistration registration; + + public void start(BundleContext context) throws Exception { + // Load mail properties + final String jettyHome = System.getProperty("jetty.home", "configuration"); + final File propertiesDir = new File(jettyHome).getParentFile(); + File propertiesfile = new File(propertiesDir, "mail.properties"); + Properties mailProperties = new Properties(); + try { + mailProperties.load(new FileReader(propertiesfile)); + } catch (IOException ioe) { + logger.log(Level.SEVERE, "Couldn't read mail properties from " + propertiesfile.getCanonicalPath(), ioe); + } + + MailService mailService = new MailServiceImpl(mailProperties); + registration = context.registerService(MailService.class, mailService, null); + final Dictionary replicableServiceProperties = new Hashtable<>(); + replicableServiceProperties.put(Replicable.OSGi_Service_Registry_ID_Property_Name, mailService.getId() + .toString()); + context.registerService(Replicable.class.getName(), mailService, replicableServiceProperties); + context.registerService(ClearStateTestSupport.class.getName(), mailService, null); + Logger.getLogger(Activator.class.getName()).info("Mail Service registered."); + } + + /* + * (non-Javadoc) + * + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext bundleContext) throws Exception { + registration.unregister(); + } + +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java new file mode 100644 index 00000000000..111a5b0c7f4 --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java @@ -0,0 +1,209 @@ +package com.sap.sse.mail.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.mail.Message.RecipientType; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import com.sap.sse.mail.MailException; +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.util.ClearStateTestSupport; + +public class MailServiceImpl implements ReplicableMailService, ClearStateTestSupport { + private static final Logger logger = Logger.getLogger(MailServiceImpl.class.getName()); + + private Properties mailProperties; + + /** + * The master from which this replicable is currently replicating, or null if this replicable is not + * currently replicated from any master. + */ + private ReplicationMasterDescriptor replicatingFromMaster; + private final ConcurrentHashMap, OperationExecutionListener> operationExecutionListeners; + private Set> operationsSentToMasterForReplication; + private ThreadLocal currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = ThreadLocal + .withInitial(() -> false); + + public MailServiceImpl(Properties mailProperties) { + this.mailProperties = mailProperties; + this.operationExecutionListeners = new ConcurrentHashMap<>(); + } + + private class SMTPAuthenticator extends javax.mail.Authenticator { + public PasswordAuthentication getPasswordAuthentication() { + String username = mailProperties.getProperty("mail.smtp.user"); + String password = mailProperties.getProperty("mail.smtp.password"); + return new PasswordAuthentication(username, password); + } + } + + private static interface ContentSetter { + void setContent(MimeMessage msg) throws MessagingException; + } + + private void internalSendMail(String toAddress, String subject, ContentSetter contentSetter) throws MailException { + if (mailProperties != null && mailProperties.containsKey("mail.transport.protocol")) { + if (toAddress != null) { + Session session = Session.getInstance(mailProperties, new SMTPAuthenticator()); + MimeMessage msg = new MimeMessage(session); + try { + msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); + msg.setSubject(subject); + contentSetter.setContent(msg); + msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); + Transport ts = session.getTransport(); + ts.connect(); + ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); + ts.close(); + logger.info("mail sent to " + toAddress + " with subject " + subject); + } catch (MessagingException e) { + logger.log(Level.SEVERE, "Error trying to send mail to " + toAddress, e); + throw new MailException(e.getMessage()); + } + } + } else { + logger.warning("No mail properties provided. Cannot send e-mail about " + + subject + + " to " + + toAddress + + ". This could also mean that this is running on a replica server in which case this is perfectly fine."); + } + } + + @Override + public Void internalSendMail(String toAddress, String subject, String body) throws MailException { + internalSendMail(toAddress, subject, new ContentSetter() { + @Override + public void setContent(MimeMessage msg) throws MessagingException { + msg.setContent(body, "text/plain"); + } + }); + return null; + } + + @Override + public void sendMail(String toAddress, String subject, String body) throws MailException { + apply(s -> s.internalSendMail(toAddress, subject, body)); + } + + @Override + public Void internalSendMail(String toAddress, String subject, Multipart multipartContent) throws MailException { + internalSendMail(toAddress, subject, new ContentSetter() { + @Override + public void setContent(MimeMessage msg) throws MessagingException { + msg.setContent(multipartContent); + } + }); + return null; + } + + @Override + public void sendMail(String toAddress, String subject, Multipart multipartContent) throws MailException { + apply(s -> s.internalSendMail(toAddress, subject, multipartContent)); + } + + // ----------------- Replication ------------- + @Override + public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException { + // do nothing + } + + @Override + public Serializable getId() { + return getClass().getName(); + } + + @Override + public ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException { + return new ObjectInputStream(is); + } + + @Override + public void initiallyFillFromInternal(ObjectInputStream is) throws IOException, ClassNotFoundException, + InterruptedException { + // send mails only from master + mailProperties = null; + } + + @Override + public void serializeForInitialReplicationInternal(ObjectOutputStream objectOutputStream) throws IOException { + // do nothing + } + + @Override + public Iterable> getOperationExecutionListeners() { + return operationExecutionListeners.keySet(); + } + + @Override + public void addOperationExecutionListener(OperationExecutionListener listener) { + operationExecutionListeners.put(listener, listener); + } + + @Override + public void removeOperationExecutionListener(OperationExecutionListener listener) { + operationExecutionListeners.remove(listener); + } + + @Override + public ReplicationMasterDescriptor getMasterDescriptor() { + return replicatingFromMaster; + } + + @Override + public void startedReplicatingFrom(ReplicationMasterDescriptor master) { + this.replicatingFromMaster = master; + } + + @Override + public void stoppedReplicatingFrom(ReplicationMasterDescriptor master) { + this.replicatingFromMaster = null; + } + + @Override + public void addOperationSentToMasterForReplication( + OperationWithResultWithIdWrapper operationWithResultWithIdWrapper) { + this.operationsSentToMasterForReplication.add(operationWithResultWithIdWrapper); + } + + @Override + public boolean hasSentOperationToMaster(OperationWithResult operation) { + return this.operationsSentToMasterForReplication.remove(operation); + } + + @Override + public boolean isCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster() { + return currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster.get(); + } + + @Override + public void setCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster( + boolean currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster) { + this.currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster + .set(currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster); + } + + @Override + public void clearState() throws Exception { + mailProperties.clear(); + } +} diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java new file mode 100644 index 00000000000..3d1508e500d --- /dev/null +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java @@ -0,0 +1,12 @@ +package com.sap.sse.mail.impl; + +import javax.mail.Multipart; + +import com.sap.sse.mail.MailException; +import com.sap.sse.mail.MailService; + +public interface ReplicableMailService extends MailService { + Void internalSendMail(String toAddress, String subject, String body) throws MailException; + + Void internalSendMail(String toAddress, String subject, Multipart multipartContent) throws MailException; +} diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java index e1b5b5a94fb..54d7628f87f 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java @@ -2,7 +2,6 @@ package com.sap.sse.security.replication.test; import java.io.IOException; import java.net.MalformedURLException; -import java.util.Properties; import com.sap.sse.mongodb.MongoDBService; import com.sap.sse.replication.testsupport.AbstractServerReplicationTest; @@ -28,14 +27,14 @@ public abstract class AbstractSecurityReplicationTest extends AbstractServerRepl @Override protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { - SecurityServiceImpl result = new SecurityServiceImpl(new UserStoreImpl(), new Properties()); + SecurityServiceImpl result = new SecurityServiceImpl(new UserStoreImpl()); result.clearReplicaState(); return result; } @Override protected SecurityServiceImpl createNewReplica() { - return new SecurityServiceImpl(new UserStoreImpl(), new Properties()); + return new SecurityServiceImpl(new UserStoreImpl()); } @Override diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java index 439018643f9..1c22fdaedae 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java @@ -9,8 +9,8 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; +import com.sap.sse.mail.MailException; import com.sap.sse.security.User; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.UserManagementException; public class SimpleSecurityReplicationTest extends AbstractSecurityReplicationTest { diff --git a/java/com.sap.sse.security.test/META-INF/MANIFEST.MF b/java/com.sap.sse.security.test/META-INF/MANIFEST.MF index a1f9525f9a2..101740dfa00 100644 --- a/java/com.sap.sse.security.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.test/META-INF/MANIFEST.MF @@ -9,4 +9,6 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.junit4;bundle-version="4.8.2", com.sap.sse.security.userstore.mongodb, com.mongodb.driver, - com.sap.sse.mongodb + com.sap.sse.mongodb, + org.mockito.mockito-core, + com.sap.sse.mail diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java index 71d9f9da6f1..5d4cf1fbee5 100644 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java @@ -6,7 +6,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.net.UnknownHostException; -import java.util.Properties; import org.apache.shiro.SecurityUtils; import org.junit.Before; @@ -40,7 +39,7 @@ public class LoginTest { UsernamePasswordRealm.setTestUserStore(store); Activator.setTestUserStore(store); Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // to enable shiro to find classes from com.sap.sse.security - new SecurityServiceImpl(store, /* mailProperties */ new Properties()); + new SecurityServiceImpl(store); } @Test diff --git a/java/com.sap.sse.security.ui/META-INF/MANIFEST.MF b/java/com.sap.sse.security.ui/META-INF/MANIFEST.MF index 347baaf4407..fb55d40f181 100644 --- a/java/com.sap.sse.security.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.ui/META-INF/MANIFEST.MF @@ -21,7 +21,8 @@ Require-Bundle: javax.servlet;bundle-version="2.5.0", org.apache.shiro.core;bundle-version="1.2.2", org.apache.shiro.web;bundle-version="1.2.2", com.sap.sse.replication, - com.sap.sse.security.common + com.sap.sse.security.common, + com.sap.sse.mail Web-ContextPath: /security/ui Export-Package: com.sap.sse.security.ui.client, com.sap.sse.security.ui.client.component, diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java index 6ae80791520..8b727a0f63f 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java @@ -4,8 +4,8 @@ import java.util.Collection; import java.util.Map; import com.google.gwt.user.client.rpc.RemoteService; +import com.sap.sse.mail.MailException; import com.sap.sse.security.shared.DefaultRoles; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.oauth.client.CredentialDTO; import com.sap.sse.security.ui.oauth.shared.OAuthException; diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index a04ae8a236d..f7949841b2a 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -27,6 +27,7 @@ import org.osgi.util.tracker.ServiceTracker; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.sap.sailing.domain.common.impl.NaturalComparator; import com.sap.sse.common.Util; +import com.sap.sse.mail.MailException; import com.sap.sse.security.Credential; import com.sap.sse.security.SecurityService; import com.sap.sse.security.SessionUtils; @@ -35,7 +36,6 @@ import com.sap.sse.security.User; import com.sap.sse.security.shared.Account; import com.sap.sse.security.shared.Account.AccountType; import com.sap.sse.security.shared.DefaultRoles; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.SocialUserAccount; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.shared.UsernamePasswordAccount; diff --git a/java/com.sap.sse.security/META-INF/MANIFEST.MF b/java/com.sap.sse.security/META-INF/MANIFEST.MF index 524101252cc..e8ca35e3510 100644 --- a/java/com.sap.sse.security/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security/META-INF/MANIFEST.MF @@ -6,7 +6,8 @@ Bundle-Version: 1.0.0.qualifier Bundle-Activator: com.sap.sse.security.impl.Activator Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: javax.mail;version="1.4.0", +Import-Package: com.sap.sse.util, + javax.mail;version="1.4.0", javax.mail.internet;version="1.4.0", javax.ws.rs;version="1.1.1", javax.ws.rs.core;version="1.1.1", @@ -31,6 +32,7 @@ Require-Bundle: javax.servlet;bundle-version="2.5.0", com.sap.sse.replication, com.sap.sse.operationaltransformation, com.sap.sse.security.common, - com.sap.sse + com.sap.sse, + com.sap.sse.mail Web-ContextPath: /security Export-Package: com.sap.sse.security diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index b3840233f21..1ac82ca09f7 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -8,11 +8,11 @@ import javax.servlet.ServletContext; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.mgt.SecurityManager; +import com.sap.sse.mail.MailException; import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; import com.sap.sse.security.impl.ReplicableSecurityService; import com.sap.sse.security.operations.SecurityOperation; import com.sap.sse.security.shared.DefaultRoles; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.SocialUserAccount; import com.sap.sse.security.shared.UserManagementException; diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java index f8c54dfdbe5..97db2ae01ee 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java @@ -1,11 +1,7 @@ package com.sap.sse.security.impl; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; import java.util.Dictionary; import java.util.Hashtable; -import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -14,11 +10,13 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.util.tracker.ServiceTracker; +import com.sap.sse.mail.MailService; import com.sap.sse.replication.Replicable; import com.sap.sse.security.SecurityService; import com.sap.sse.security.UserStore; import com.sap.sse.security.UsernamePasswordRealm; import com.sap.sse.util.ClearStateTestSupport; +import com.sap.sse.util.ServiceTrackerFactory; public class Activator implements BundleActivator { private static final Logger logger = Logger.getLogger(Activator.class.getName()); @@ -57,25 +55,15 @@ public class Activator implements BundleActivator { * registered as an OSGi service. */ public void start(BundleContext bundleContext) throws Exception { - // Load mail properties - final String jettyHome = System.getProperty("jetty.home", "configuration"); - final File propertiesDir = new File(jettyHome).getParentFile(); - File propertiesfile = new File(propertiesDir, "security.properties"); - Properties mailProperties = new Properties(); - try { - mailProperties.load(new FileReader(propertiesfile)); - } catch (IOException ioe) { - logger.log(Level.SEVERE, "Couldn't read security properties from "+propertiesfile.getCanonicalPath(), ioe); - } if (testUserStore != null) { - createAndRegisterSecurityService(testUserStore, mailProperties); + createAndRegisterSecurityService(bundleContext, testUserStore); } else { - waitForUserStoreService(bundleContext, mailProperties); + waitForUserStoreService(bundleContext); } } - private void createAndRegisterSecurityService(UserStore store, Properties mailProperties) { - securityService = new SecurityServiceImpl(store, mailProperties); + private void createAndRegisterSecurityService(BundleContext bundleContext, UserStore store) { + securityService = new SecurityServiceImpl(ServiceTrackerFactory.createAndOpen(context, MailService.class), store); registration = context.registerService(SecurityService.class.getName(), securityService, null); final Dictionary replicableServiceProperties = new Hashtable<>(); @@ -85,7 +73,7 @@ public class Activator implements BundleActivator { Logger.getLogger(Activator.class.getName()).info("Security Service registered."); } - private void waitForUserStoreService(BundleContext bundleContext, final Properties mailProperties) { + private void waitForUserStoreService(BundleContext bundleContext) { context = bundleContext; final ServiceTracker tracker = new ServiceTracker<>(bundleContext, UserStore.class, /* customizer */ null); tracker.open(); @@ -97,7 +85,7 @@ public class Activator implements BundleActivator { logger.info("Waiting for UserStore service..."); UserStore userStore = tracker.waitForService(0); logger.info("Obtained UserStore service "+userStore); - createAndRegisterSecurityService(userStore, mailProperties); + createAndRegisterSecurityService(bundleContext, userStore); } catch (InterruptedException e) { logger.log(Level.SEVERE, "Interrupted while waiting for UserStore service", e); } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index db50118e4bc..314e5baea7a 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -13,20 +13,12 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; -import java.util.Properties; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; -import javax.mail.Message.RecipientType; -import javax.mail.MessagingException; -import javax.mail.PasswordAuthentication; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; import javax.servlet.Filter; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -54,6 +46,7 @@ import org.apache.shiro.web.filter.mgt.FilterChainResolver; import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; import org.apache.shiro.web.util.SavedRequest; import org.apache.shiro.web.util.WebUtils; +import org.osgi.util.tracker.ServiceTracker; import org.scribe.builder.ServiceBuilder; import org.scribe.builder.api.FacebookApi; import org.scribe.builder.api.FlickrApi; @@ -71,6 +64,8 @@ import org.scribe.oauth.OAuthService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.sap.sse.common.Util; +import com.sap.sse.mail.MailException; +import com.sap.sse.mail.MailService; import com.sap.sse.replication.OperationExecutionListener; import com.sap.sse.replication.OperationWithResult; import com.sap.sse.replication.ReplicationMasterDescriptor; @@ -89,7 +84,6 @@ import com.sap.sse.security.User; import com.sap.sse.security.UserStore; import com.sap.sse.security.shared.Account.AccountType; import com.sap.sse.security.shared.DefaultRoles; -import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.SocialUserAccount; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.shared.UsernamePasswordAccount; @@ -111,7 +105,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica private final ReplicatingCacheManager cacheManager; private UserStore store; - private final Properties mailProperties; + private final ServiceTracker mailServiceTracker; private final ConcurrentHashMap, OperationExecutionListener> operationExecutionListeners; /** @@ -129,18 +123,21 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica shiroConfiguration = new Ini(); shiroConfiguration.loadFromPath("classpath:shiro.ini"); } + + public SecurityServiceImpl(UserStore store) { + this(null, store); + } /** * @param mailProperties must not be null */ - public SecurityServiceImpl(UserStore store, Properties mailProperties) { - assert mailProperties != null; - logger.info("Initializing Security Service with user store " + store+" and mail properties "+mailProperties); + public SecurityServiceImpl(ServiceTracker mailServiceTracker, UserStore store) { + logger.info("Initializing Security Service with user store " + store); operationsSentToMasterForReplication = new HashSet<>(); cacheManager = new ReplicatingCacheManager(); this.operationExecutionListeners = new ConcurrentHashMap<>(); this.store = store; - this.mailProperties = mailProperties; + this.mailServiceTracker = mailServiceTracker; // Create default users if no users exist yet. initEmptyStore(); Factory factory = new WebIniSecurityManagerFactory(shiroConfiguration); @@ -189,44 +186,14 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica } } - private class SMTPAuthenticator extends javax.mail.Authenticator { - public PasswordAuthentication getPasswordAuthentication() { - String username = mailProperties.getProperty("mail.smtp.user"); - String password = mailProperties.getProperty("mail.smtp.password"); - return new PasswordAuthentication(username, password); - } - } - @Override public void sendMail(String username, String subject, String body) throws MailException { - if (this.mailProperties != null && this.mailProperties.containsKey("mail.transport.protocol")) { - final User user = getUserByName(username); - if (user != null) { - final String toAddress = user.getEmail(); - if (toAddress != null) { - Session session = Session.getInstance(this.mailProperties, new SMTPAuthenticator()); - MimeMessage msg = new MimeMessage(session); - try { - msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); - msg.setSubject(subject); - msg.setContent(body, "text/plain"); - msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); - Transport ts = session.getTransport(); - ts.connect(); - ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); - ts.close(); - logger.info("mail sent to user " + username + " with e-mail address " + toAddress - + " with subject " + subject); - } catch (MessagingException e) { - logger.log(Level.SEVERE, "Error trying to send mail to user " + username - + " with e-mail address " + toAddress, e); - throw new MailException(e.getMessage()); - } - } + final User user = getUserByName(username); + if (user != null) { + final String toAddress = user.getEmail(); + if (toAddress != null) { + mailServiceTracker.getService().sendMail(username, subject, body); } - } else { - logger.warning("No mail properties provided. Cannot send e-mail about "+subject+" to user "+username+ - ". This could also mean that this is running on a replica server in which case this is perfectly fine."); } } @@ -904,7 +871,6 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica // ----------------- Replication ------------- @Override public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException { - mailProperties.clear(); store.clear(); } diff --git a/java/com.sap.sse/src/com/sap/sse/util/ServiceTrackerFactory.java b/java/com.sap.sse/src/com/sap/sse/util/ServiceTrackerFactory.java new file mode 100644 index 00000000000..3af71035c02 --- /dev/null +++ b/java/com.sap.sse/src/com/sap/sse/util/ServiceTrackerFactory.java @@ -0,0 +1,18 @@ +package com.sap.sse.util; + +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +public class ServiceTrackerFactory { + /** + * @return {@code null} if {@code context} is {@code null} + */ + public static ServiceTracker createAndOpen(BundleContext context, Class clazz) { + if (context == null) { + return null; + } + ServiceTracker result = new ServiceTracker(context, clazz, null); + result.open(); + return result; + } +} diff --git a/java/pom.xml b/java/pom.xml index ed10a73d536..c8e44dced62 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -17,6 +17,7 @@ com.sap.sse com.sap.sse.common com.sap.sse.shared.android + com.sap.sse.mail com.sap.sse.mongodb com.sap.sse.gwt com.sap.sse.gwt.adminconsole diff --git a/java/target/configuration/security.properties b/java/target/configuration/mail.properties similarity index 100% rename from java/target/configuration/security.properties rename to java/target/configuration/mail.properties diff --git a/java/target/env.sh b/java/target/env.sh index 8ef97faa381..80556db3319 100644 --- a/java/target/env.sh +++ b/java/target/env.sh @@ -41,7 +41,7 @@ EXPEDITION_PORT=2010 # set the REPLICATE_MASTER_EXCHANGE_NAME variable to the # same channel the master is using in its REPLICATION_CHANNEL variable -# REPLICATE_ON_START=com.sap.sailing.server.impl.RacingEventServiceImpl,com.sap.sse.security.impl.SecurityServiceImpl,com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl +# REPLICATE_ON_START=com.sap.sailing.server.impl.RacingEventServiceImpl,com.sap.sse.security.impl.SecurityServiceImpl,com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl,com.sap.sse.mail.impl.MailServiceImpl # Host where the master Java instance is running # Make sure firewall configurations allow access From a8415424194efc340d75664805575fb206e6ff34 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 21:09:04 +0100 Subject: [PATCH 040/264] make MailService known in SailingService --- java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF | 3 ++- .../com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 0da4d4e7cd2..f4d8f1c11b8 100755 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -45,7 +45,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.operationaltransformation, com.sap.sse, com.sap.sse.security.common, - com.sap.sse.filestorage + com.sap.sse.filestorage, + com.sap.sse.mail Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy Import-Package: com.sap.sailing.polars, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 5e50344d0d5..121326f19b9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -424,6 +424,7 @@ import com.sap.sse.filestorage.InvalidPropertiesException; import com.sap.sse.gwt.server.filestorage.FileStorageServiceDTOUtils; import com.sap.sse.gwt.shared.filestorage.FileStorageServiceDTO; import com.sap.sse.gwt.shared.filestorage.FileStorageServicePropertyErrorsDTO; +import com.sap.sse.mail.MailService; import com.sap.sse.replication.OperationWithResult; import com.sap.sse.replication.ReplicationFactory; import com.sap.sse.replication.ReplicationMasterDescriptor; @@ -447,6 +448,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final ServiceTracker resultUrlRegistryServiceTracker; private final ServiceTracker scoreCorrectionProviderServiceTracker; + + private final ServiceTracker mailServiceTracker; private final MongoObjectFactory mongoObjectFactory; @@ -521,6 +524,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S .getService().createSwissTimingReplayService(getSwissTimingAdapter().getSwissTimingDomainFactory()); scoreCorrectionProviderServiceTracker = ServiceTrackerFactory.createAndOpen(context, ScoreCorrectionProvider.class); + mailServiceTracker = ServiceTrackerFactory.createAndOpen(context, MailService.class); tractracDomainObjectFactory = com.sap.sailing.domain.tractracadapter.persistence.PersistenceFactory.INSTANCE .createDomainObjectFactory(mongoObjectFactory.getDatabase(), getTracTracAdapter() .getTracTracDomainFactory()); @@ -574,6 +578,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S protected TracTracAdapter getTracTracAdapter() { return getTracTracAdapterFactory().getOrCreateTracTracAdapter(baseDomainFactory); } + + protected MailService getMailService() { + return mailServiceTracker.getService(); + } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); From 1cacd1e12f139fa3bbbd3222f8933bcd5ac5c93c Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 21:09:26 +0100 Subject: [PATCH 041/264] start MailService during selenium test --- java/com.sap.sailing.selenium.test/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/java/com.sap.sailing.selenium.test/pom.xml b/java/com.sap.sailing.selenium.test/pom.xml index b6a347abc8d..64e11bbd8b4 100644 --- a/java/com.sap.sailing.selenium.test/pom.xml +++ b/java/com.sap.sailing.selenium.test/pom.xml @@ -88,6 +88,11 @@ 3 true + + com.sap.sse.mail + 4 + true + com.sap.sse.security 3 From 354d284b0363243cfd2259d8fb4cfee60e8b0b17 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 21:09:58 +0100 Subject: [PATCH 042/264] add Mail bundle to launch configs --- java/com.sap.sailing.server/SailingServer (No Proxy).launch | 2 +- java/com.sap.sailing.server/SailingServer (Proxy).launch | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 8d2cd64f937..397e0528535 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -33,5 +33,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 4addb3b7f41..d2c5026a035 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -33,6 +33,6 @@ - + From 0f22ef64dfcc4a3f01fe4efaaa7c4cf6d227d1b4 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 21:39:36 +0100 Subject: [PATCH 043/264] Revert "make MailService known in SailingService" This reverts commit a8415424194efc340d75664805575fb206e6ff34. --- java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF | 3 +-- .../com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index f4d8f1c11b8..0da4d4e7cd2 100755 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -45,8 +45,7 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.operationaltransformation, com.sap.sse, com.sap.sse.security.common, - com.sap.sse.filestorage, - com.sap.sse.mail + com.sap.sse.filestorage Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy Import-Package: com.sap.sailing.polars, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 121326f19b9..5e50344d0d5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -424,7 +424,6 @@ import com.sap.sse.filestorage.InvalidPropertiesException; import com.sap.sse.gwt.server.filestorage.FileStorageServiceDTOUtils; import com.sap.sse.gwt.shared.filestorage.FileStorageServiceDTO; import com.sap.sse.gwt.shared.filestorage.FileStorageServicePropertyErrorsDTO; -import com.sap.sse.mail.MailService; import com.sap.sse.replication.OperationWithResult; import com.sap.sse.replication.ReplicationFactory; import com.sap.sse.replication.ReplicationMasterDescriptor; @@ -448,8 +447,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final ServiceTracker resultUrlRegistryServiceTracker; private final ServiceTracker scoreCorrectionProviderServiceTracker; - - private final ServiceTracker mailServiceTracker; private final MongoObjectFactory mongoObjectFactory; @@ -524,7 +521,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S .getService().createSwissTimingReplayService(getSwissTimingAdapter().getSwissTimingDomainFactory()); scoreCorrectionProviderServiceTracker = ServiceTrackerFactory.createAndOpen(context, ScoreCorrectionProvider.class); - mailServiceTracker = ServiceTrackerFactory.createAndOpen(context, MailService.class); tractracDomainObjectFactory = com.sap.sailing.domain.tractracadapter.persistence.PersistenceFactory.INSTANCE .createDomainObjectFactory(mongoObjectFactory.getDatabase(), getTracTracAdapter() .getTracTracDomainFactory()); @@ -578,10 +574,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S protected TracTracAdapter getTracTracAdapter() { return getTracTracAdapterFactory().getOrCreateTracTracAdapter(baseDomainFactory); } - - protected MailService getMailService() { - return mailServiceTracker.getService(); - } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); From e58f2931eaa686c2cc3e6e675c6c296738f0fcc1 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 22:28:13 +0100 Subject: [PATCH 044/264] move email invite functionality into racelog tracking adapter --- .../META-INF/MANIFEST.MF | 9 +- .../RaceLogTrackingAdapter.java | 53 +++++----- .../racelogtracking/impl/Activator.java | 10 ++ .../impl/RaceLogTrackingAdapterImpl.java | 82 ++++++++++++++++ .../META-INF/MANIFEST.MF | 4 - .../sailing/gwt/ui/client/SailingService.java | 2 +- .../gwt/ui/client/SailingServiceAsync.java | 4 +- .../gwt/ui/server/SailingServiceImpl.java | 98 +------------------ 8 files changed, 136 insertions(+), 126 deletions(-) diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF index 8e31d895c72..a73e519d31c 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/META-INF/MANIFEST.MF @@ -17,8 +17,13 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.common, com.sap.sse.replication, com.sap.sse, - com.sap.sailing.server.gateway.serialization.shared.android -Import-Package: com.mongodb, + com.sap.sailing.server.gateway.serialization.shared.android, + com.sap.sse.mail +Import-Package: com.google.zxing;version="3.1.0", + com.mongodb, + javax.mail;version="1.4.0", + javax.mail.internet;version="1.4.0", + javax.mail.util;version="1.4.0", javax.servlet;version="2.6.0", org.json.simple, org.json.simple.parser, diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java index 56b300f4aca..c84223ec45e 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java @@ -10,6 +10,7 @@ import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEven import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.ControlPoint; +import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumn; @@ -24,12 +25,14 @@ import com.sap.sailing.domain.racelogtracking.impl.RaceLogRaceTracker; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.server.RacingEventService; +import com.sap.sse.mail.MailException; -public interface RaceLogTrackingAdapter { +public interface RaceLogTrackingAdapter { /** - * Performs the necessary steps to ensure that the race is tracked (aka that a {@link TrackedRace} - * is created from the data in this {@code RaceLog}).

- * The following steps are performed to achieve this: + * Performs the necessary steps to ensure that the race is tracked (aka that a {@link TrackedRace} is created from + * the data in this {@code RaceLog}). + *

+ * The following steps are performed to achieve this: *

    *
  • Is the racelog denoted for tracking? If not, throw exception.
  • *
  • Is a {@link RaceLogRaceTracker} already listening for this racelog? If not, add one.
  • @@ -38,7 +41,7 @@ public interface RaceLogTrackingAdapter { */ void startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet) throws NotDenotedForRaceLogTrackingException, Exception; - + RaceLogTrackingState getRaceLogTrackingState(RacingEventService service, RaceColumn raceColumn, Fleet fleet); /** @@ -51,16 +54,15 @@ public interface RaceLogTrackingAdapter { * * @throws NotDenotableForRaceLogTrackingException * Fails, if no {@link RaceLog}, or a non-empty {@link RaceLog}, or one with attached - * {@link TrackedRace} is found already in place. - * Also fails, if the {@code leaderboard} is not a {@link RegattaLeaderboard}. + * {@link TrackedRace} is found already in place. Also fails, if the {@code leaderboard} is not a + * {@link RegattaLeaderboard}. */ void denoteRaceForRaceLogTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet, String raceName) throws NotDenotableForRaceLogTrackingException; - + /** - * Revoke the {@link RaceLogDenoteForTrackingEvent}, and if it exists the {@link RaceLogStartTrackingEvent}. - * This does not affect existing an {@link RaceLogRaceTracker} - * or {@link TrackedRace} for this {@code RaceLog}. + * Revoke the {@link RaceLogDenoteForTrackingEvent}, and if it exists the {@link RaceLogStartTrackingEvent}. This + * does not affect existing an {@link RaceLogRaceTracker} or {@link TrackedRace} for this {@code RaceLog}. */ void removeDenotationForRaceLogTracking(RacingEventService service, RaceLog raceLog); @@ -71,30 +73,35 @@ public interface RaceLogTrackingAdapter { */ void denoteAllRacesForRaceLogTracking(RacingEventService service, Leaderboard leaderboard) throws NotDenotableForRaceLogTrackingException; - + /** * Add a fix to the {@link GPSFixStore}, and create a mapping with a virtual device for exactly that timepoint. */ void pingMark(RaceLog raceLogToAddTo, Mark mark, GPSFix gpsFix, RacingEventService service); - + /** - * Duplicate the course and competitor registrations in the newest {@link RaceLogCourseDesignChangedEvent} - * in {@code from} race log to the {@code to} race logs. - * The {@link Mark}s and {@link ControlPoint}s are duplicated and not reused. This also inserts the necessary - * {@link RaceLogDefineMarkEvent}s into the {@code to} race logs. + * Duplicate the course and competitor registrations in the newest {@link RaceLogCourseDesignChangedEvent} in + * {@code from} race log to the {@code to} race logs. The {@link Mark}s and {@link ControlPoint}s are duplicated and + * not reused. This also inserts the necessary {@link RaceLogDefineMarkEvent}s into the {@code to} race logs. */ void copyCourseAndCompetitors(RaceLog from, Set to, SharedDomainFactory baseDomainFactory, RacingEventService service); - + /** - * If not yet registered, register the competitors in {@code competitors}, - * and unregister all already registered competitors not in {@code competitors}. + * If not yet registered, register the competitors in {@code competitors}, and unregister all already registered + * competitors not in {@code competitors}. */ void registerCompetitors(RacingEventService service, RaceLog raceLog, Set competitors); - + /** - * If not yet registered, register the competitors in {@code competitors}, - * and unregister all already registered competitors not in {@code competitors}. + * If not yet registered, register the competitors in {@code competitors}, and unregister all already registered + * competitors not in {@code competitors}. */ void registerCompetitors(RacingEventService service, RegattaLog regattaLog, Set competitors); + + /** + * Invite all competitors known in the leaderboard for tracking via the Tracking App by sending out emails. + */ + void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard, String serverUrlWithoutTrailingSlash) + throws MailException; } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java index c66d80ac9c5..e26a03e2f62 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java @@ -36,6 +36,12 @@ import com.sap.sse.common.TypeBasedServiceFinder; public class Activator implements BundleActivator { private static final Logger logger = Logger.getLogger(Activator.class.getName()); + + private static BundleContext context; + + public static BundleContext getContext() { + return context; + } private Set> registrations = new HashSet<>(); @@ -53,6 +59,8 @@ public class Activator implements BundleActivator { @Override public void start(BundleContext context) throws Exception { + Activator.context = context; + registrations.add(context.registerService(DeviceIdentifierMongoHandler.class, new SmartphoneUUIDMongoHandler(), getDict(SmartphoneUUIDIdentifier.TYPE))); registrations.add(context.registerService(DeviceIdentifierJsonHandler.class, new SmartphoneUUIDJsonHandler(), getDict(SmartphoneUUIDIdentifier.TYPE))); registrations.add(context.registerService(DeviceIdentifierStringSerializationHandler.class, new SmartphoneUUIDStringSerializationHandler(), getDict(SmartphoneUUIDIdentifier.TYPE))); @@ -75,6 +83,8 @@ public class Activator implements BundleActivator { @Override public void stop(BundleContext context) throws Exception { + Activator.context = null; + for (ServiceRegistration reg : registrations) { reg.unregister(); } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index 07317ec94ed..2c405759a54 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.racelogtracking.impl; +import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -10,6 +12,17 @@ import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.mail.BodyPart; +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMultipart; +import javax.mail.util.ByteArrayDataSource; + +import org.osgi.framework.ServiceReference; + +import com.google.zxing.WriterException; import com.sap.sailing.domain.abstractlog.AbstractLog; import com.sap.sailing.domain.abstractlog.AbstractLogEvent; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; @@ -32,6 +45,7 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.ControlPoint; import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumn; @@ -42,6 +56,7 @@ import com.sap.sailing.domain.base.impl.CourseDataImpl; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.abstractlog.NotRevokableException; +import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.common.racelog.tracking.NotDenotableForRaceLogTrackingException; import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; import com.sap.sailing.domain.common.racelog.tracking.RaceLogRaceTrackerExistsException; @@ -61,6 +76,9 @@ import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.qrcode.QRCodeGenerationUtil; +import com.sap.sse.mail.MailException; +import com.sap.sse.mail.MailService; public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { private static final Logger logger = Logger.getLogger(RaceLogTrackingAdapterImpl.class.getName()); @@ -325,4 +343,68 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { c -> new RegattaLogRegisterCompetitorEventImpl(MillisecondsTimePoint.now(), service.getServerAuthor(), MillisecondsTimePoint.now(), UUID.randomUUID(), c)); } + + private MailService getMailService() { + ServiceReference ref = Activator.getContext() + .getServiceReference(MailService.class); + if (ref == null) { + logger.warning("No file storage management service registered"); + return null; + } + return Activator.getContext().getService(ref); + } + + @Override + public void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard, + String serverUrlWithoutTrailingSlash) throws MailException { + //TODO check bug 2587 + Iterable competitors = leaderboard.getAllCompetitors(); + + + StringBuilder occuredExceptions = new StringBuilder(); + + for (Competitor competitor : competitors){ + final String toAddress = competitor.getEmail(); + if (toAddress != null) { + String leaderboardName = leaderboard.getName(); + String competitorName = competitor.getName(); + + //TODO i18n + String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash, + event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING, + competitor.getId().toString()); + String subject = "Tracking invitation for " + competitorName; + + // taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm + BodyPart messageTextPart = new MimeBodyPart(); + String htmlText = String.format("

    Welcome to %s

    " + + "

    Scan the following QRCode or visit the URL using your smartphone to register as %s

    " + + "" + + "%s", leaderboardName, competitorName, url, url); + + try { + messageTextPart.setContent(htmlText, "text/html"); + + BodyPart messageImagePart = new MimeBodyPart(); + InputStream imageIs = QRCodeGenerationUtil.create(url, 64); + DataSource imageDs = new ByteArrayDataSource(imageIs, "image/png"); + messageImagePart.setDataHandler(new DataHandler(imageDs)); + messageImagePart.setHeader("Content-ID", ""); + + MimeMultipart multipart = new MimeMultipart(); + multipart.addBodyPart(messageTextPart); + multipart.addBodyPart(messageImagePart); + + getMailService().sendMail(toAddress, subject, multipart); + } catch (MessagingException | MailException | WriterException | IOException e) { + logger.log(Level.SEVERE, "Error trying to send mail to " + competitor.getName() + + " with e-mail address " + toAddress, e); + occuredExceptions.append(e.getMessage()+"\r\n"); + } + } + } + if (!(occuredExceptions.length() == 0)){ + throw new MailException(occuredExceptions.toString()); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 88b44d6de87..f4d8f1c11b8 100755 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -46,7 +46,6 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse, com.sap.sse.security.common, com.sap.sse.filestorage, - org.apache.servicemix.bundles.zxing, com.sap.sse.mail Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy @@ -54,9 +53,6 @@ Import-Package: com.sap.sailing.polars, com.sap.sailing.polars.factory, com.sap.sailing.polars.regression, javax.jms;version="1.1.0", - javax.mail;version="1.4.0", - javax.mail.internet;version="1.4.0", - javax.mail.util;version="1.4.0", javax.servlet;version="3.1.0", javax.servlet.descriptor;version="3.1.0", javax.servlet.http;version="3.1.0", diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 1be4ce3d6c5..a72bc5e9d77 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -577,6 +577,6 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS List getDeviceMappingsFromLogHierarchy(String leaderboardName, String raceColumnName, String fleetName) throws TransformationException; - void inviteCompetitorsViaEmail(String serverUrlWithoutTrailingSlash, String eventId, String leaderboardName) + void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, String leaderboardName) throws MailException; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 38da2adc267..0eecf745f99 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -688,6 +688,6 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM void getActiveFileStorageServiceName(AsyncCallback callback); - void inviteCompetitorsViaEmail(String serverUrlWithoutTrailingSlash, String eventId, String leaderboardName, - AsyncCallback callback); + void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, + String leaderboardName, AsyncCallback callback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 2e9d35064de..76a14c924e5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -50,19 +50,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; -import javax.activation.DataHandler; -import javax.activation.DataSource; -import javax.mail.BodyPart; -import javax.mail.MessagingException; -import javax.mail.PasswordAuthentication; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMessage.RecipientType; -import javax.mail.internet.MimeMultipart; -import javax.mail.util.ByteArrayDataSource; import javax.servlet.ServletContext; import org.apache.http.client.ClientProtocolException; @@ -71,7 +58,6 @@ import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; -import com.google.zxing.WriterException; import com.sap.sailing.domain.abstractlog.AbstractLog; import com.sap.sailing.domain.abstractlog.MultiLogAnalyzer; import com.sap.sailing.domain.abstractlog.Revokable; @@ -208,7 +194,6 @@ import com.sap.sailing.domain.common.racelog.FlagPole; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; -import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice; import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; @@ -436,7 +421,6 @@ import com.sap.sse.common.Util.Triple; import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.TimeRangeImpl; -import com.sap.sse.common.qrcode.QRCodeGenerationUtil; import com.sap.sse.common.search.KeywordQuery; import com.sap.sse.common.search.Result; import com.sap.sse.filestorage.FileStorageService; @@ -5452,86 +5436,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return null; } } - - - private class SMTPAuthenticator extends javax.mail.Authenticator { - - public PasswordAuthentication getPasswordAuthentication() { - String username = mailProperties.getProperty("mail.smtp.user"); - String password = mailProperties.getProperty("mail.smtp.password"); - return new PasswordAuthentication(username, password); - } - } @Override - public void inviteCompetitorsViaEmail(String serverUrlWithoutTrailingSlash, String eventId, + public void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, String leaderboardName) throws MailException { - if (!(this.mailProperties != null && this.mailProperties.containsKey("mail.transport.protocol"))) { - logger.severe("Could not find valid email properties to send competitor invitation E-Mail"); - return; - } - - //TODO check bug 2587 - Iterable competitors = getService().getLeaderboardByName(leaderboardName).getAllCompetitors(); - - - StringBuilder occuredExceptions = new StringBuilder(); - - for (Competitor competitor : competitors){ - final String toAddress = competitor.getEmail(); - if (toAddress != null) { - Session session = Session.getInstance(this.mailProperties, new SMTPAuthenticator()); - MimeMessage msg = new MimeMessage(session); - try { - msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); - msg.setSubject("Invitation to a SAP Sailing Analytics event"); //FIXME: I18N? - msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); - - // taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm - // This mail has 2 part, the BODY and the embedded image - MimeMultipart multipart = new MimeMultipart("related"); - - // first part (the html) - BodyPart messageBodyPart = new MimeBodyPart(); - String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash, - eventId, leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING, - competitor.getId().toString()); - String htmlText = String.format("

    Welcome to %s

    " - + "

    Scan the following QRCode or visit the URL using your smartphone to register as %s

    " - + "" - + "%s", leaderboardName, competitor.getName(), url, url); - messageBodyPart.setContent(htmlText, "text/html"); - // add it - multipart.addBodyPart(messageBodyPart); - - // second part (the image) - messageBodyPart = new MimeBodyPart(); - InputStream imageIs = QRCodeGenerationUtil.create(url, 64); - DataSource imageDs = new ByteArrayDataSource(imageIs, "image/png"); - - messageBodyPart.setDataHandler(new DataHandler(imageDs)); - messageBodyPart.setHeader("Content-ID", ""); - - // add image to the multipart - multipart.addBodyPart(messageBodyPart); - - // put everything together - msg.setContent(multipart); - - Transport ts = session.getTransport(); - ts.connect(); - ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); - ts.close(); - logger.info("mail sent to competitor " + competitor.getName() + " with e-mail address " + toAddress); - } catch (MessagingException | WriterException | IOException e) { - logger.log(Level.SEVERE, "Error trying to send mail to user " + competitor.getName() - + " with e-mail address " + toAddress, e); - occuredExceptions.append(e.getMessage()+"\r\n"); - } - } - } - if (!(occuredExceptions.length() == 0)){ - throw new MailException(occuredExceptions.toString()); - } + Event event = getService().getEvent(eventId); + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + getRaceLogTrackingAdapter().inviteCompetitorsForTrackingViaEmail(event, leaderboard, serverUrlWithoutTrailingSlash); } } From c6b68ee17b0ada3b362d6d73b5a31c2c22223a9a Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 22:36:55 +0100 Subject: [PATCH 045/264] move MailException to common bundle (needed by GWT) --- java/com.sap.sse.common/META-INF/MANIFEST.MF | 1 + .../src/com/sap/sse/common}/mail/MailException.java | 2 +- java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java | 1 + .../src/com/sap/sse/mail/impl/MailServiceImpl.java | 2 +- .../src/com/sap/sse/mail/impl/ReplicableMailService.java | 2 +- .../replication/test/SimpleSecurityReplicationTest.java | 2 +- .../com/sap/sse/security/ui/client/UserManagementService.java | 2 +- .../sap/sse/security/ui/server/UserManagementServiceImpl.java | 2 +- .../src/com/sap/sse/security/SecurityService.java | 2 +- .../src/com/sap/sse/security/impl/SecurityServiceImpl.java | 2 +- 10 files changed, 10 insertions(+), 8 deletions(-) rename java/{com.sap.sse.mail/src/com/sap/sse => com.sap.sse.common/src/com/sap/sse/common}/mail/MailException.java (88%) diff --git a/java/com.sap.sse.common/META-INF/MANIFEST.MF b/java/com.sap.sse.common/META-INF/MANIFEST.MF index 56290d7ee65..d41f336204f 100644 --- a/java/com.sap.sse.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.common/META-INF/MANIFEST.MF @@ -8,6 +8,7 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Export-Package: com.sap.sse.common, com.sap.sse.common.filter, com.sap.sse.common.impl, + com.sap.sse.common.mail, com.sap.sse.common.scalablevalue, com.sap.sse.common.search Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0" diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailException.java b/java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java similarity index 88% rename from java/com.sap.sse.mail/src/com/sap/sse/mail/MailException.java rename to java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java index 79013c8aebb..84c9ab3e5e4 100755 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailException.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java @@ -1,4 +1,4 @@ -package com.sap.sse.mail; +package com.sap.sse.common.mail; import java.io.Serializable; diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java index 810f473c0c1..6578e8b05d6 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java @@ -2,6 +2,7 @@ package com.sap.sse.mail; import javax.mail.Multipart; +import com.sap.sse.common.mail.MailException; import com.sap.sse.mail.impl.ReplicableMailService; import com.sap.sse.replication.Replicable; import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java index 111a5b0c7f4..7ef047c3a5c 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java @@ -21,7 +21,7 @@ import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; -import com.sap.sse.mail.MailException; +import com.sap.sse.common.mail.MailException; import com.sap.sse.replication.OperationExecutionListener; import com.sap.sse.replication.OperationWithResult; import com.sap.sse.replication.ReplicationMasterDescriptor; diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java index 3d1508e500d..116ae1f798c 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/ReplicableMailService.java @@ -2,7 +2,7 @@ package com.sap.sse.mail.impl; import javax.mail.Multipart; -import com.sap.sse.mail.MailException; +import com.sap.sse.common.mail.MailException; import com.sap.sse.mail.MailService; public interface ReplicableMailService extends MailService { diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java index 1c22fdaedae..08b962910ce 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java @@ -9,7 +9,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import com.sap.sse.mail.MailException; +import com.sap.sse.common.mail.MailException; import com.sap.sse.security.User; import com.sap.sse.security.shared.UserManagementException; diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java index 8b727a0f63f..01a4f952a44 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java @@ -4,7 +4,7 @@ import java.util.Collection; import java.util.Map; import com.google.gwt.user.client.rpc.RemoteService; -import com.sap.sse.mail.MailException; +import com.sap.sse.common.mail.MailException; import com.sap.sse.security.shared.DefaultRoles; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.oauth.client.CredentialDTO; diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index f7949841b2a..31ef0aa6901 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -27,7 +27,7 @@ import org.osgi.util.tracker.ServiceTracker; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.sap.sailing.domain.common.impl.NaturalComparator; import com.sap.sse.common.Util; -import com.sap.sse.mail.MailException; +import com.sap.sse.common.mail.MailException; import com.sap.sse.security.Credential; import com.sap.sse.security.SecurityService; import com.sap.sse.security.SessionUtils; diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index 1ac82ca09f7..237dcc46241 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -8,7 +8,7 @@ import javax.servlet.ServletContext; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.mgt.SecurityManager; -import com.sap.sse.mail.MailException; +import com.sap.sse.common.mail.MailException; import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; import com.sap.sse.security.impl.ReplicableSecurityService; import com.sap.sse.security.operations.SecurityOperation; diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index 314e5baea7a..00ba1efcd8a 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -64,7 +64,7 @@ import org.scribe.oauth.OAuthService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.sap.sse.common.Util; -import com.sap.sse.mail.MailException; +import com.sap.sse.common.mail.MailException; import com.sap.sse.mail.MailService; import com.sap.sse.replication.OperationExecutionListener; import com.sap.sse.replication.OperationWithResult; From 7c3ec1983f80684a517446323bfa5079524521c0 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 22:59:07 +0100 Subject: [PATCH 046/264] explain how to get Locale from GWT in JavaDoc --- .../com/sap/sse/i18n/ResourceBundleStringMessages.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java b/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java index b752847d119..7db6f7f48d0 100644 --- a/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java +++ b/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java @@ -6,6 +6,16 @@ import java.util.Map; /** * Allow server-side internationalization similar to GWT client-side by using property files. + * + * Get Locale in GWT-Context by calling + *
    + * LocaleInfo.getCurrentLocale().getLocaleName();
    + * 
    + * + * Then transform back to {@link Locale} on server by calling + *
    + * ResourceBundleStringMessages.Util.getLocaleFor(localeInfoName);
    + * 
    */ public interface ResourceBundleStringMessages { From 2f1b4390b4888454414a97a91c0cb653ceae47c5 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 22:59:28 +0100 Subject: [PATCH 047/264] add i18n to tracking invitation emails --- .../.classpath | 1 + .../org.eclipse.core.resources.prefs | 2 ++ .../build.properties | 3 ++- .../TrackingStringMessages_de.properties | 3 +++ .../TrackingStringMessages_en.properties | 3 +++ .../RaceLogTrackingAdapter.java | 5 +++-- .../impl/RaceLogTrackingAdapterImpl.java | 19 +++++++++++-------- .../impl/RaceLogTrackingI18n.java | 11 +++++++++++ .../sailing/gwt/ui/client/SailingService.java | 4 ++-- .../gwt/ui/client/SailingServiceAsync.java | 2 +- .../gwt/ui/server/SailingServiceImpl.java | 15 +++++++++------ 11 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs create mode 100644 java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_de.properties create mode 100644 java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_en.properties create mode 100644 java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingI18n.java diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath b/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath index 4f83b2397ec..03c4ce9fdd8 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.classpath @@ -2,6 +2,7 @@ + diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..43eec3e2c6e --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding//resources/TrackingStringMessages_de.properties=UTF-8 diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties index 34d2e4d2dad..f11210d3f2c 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/build.properties @@ -1,4 +1,5 @@ -source.. = src/ +source.. = src/,\ + resources/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_de.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_de.properties new file mode 100644 index 00000000000..3f4537991ef --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_de.properties @@ -0,0 +1,3 @@ +trackingInvitationFor=Einladung zum Tracken für +scanQRCodeOrVisitUrlToRegisterAs=Scanne den QRCode oder besuche die URL mit deinem Smartphone um dich zu registrieren als +welcomeTo=Willkommen zu \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_en.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_en.properties new file mode 100644 index 00000000000..a0993d18681 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_en.properties @@ -0,0 +1,3 @@ +trackingInvitationFor=Tracking Invitation for +scanQRCodeOrVisitUrlToRegisterAs=Scan the following QRCode or visit the URL using your smartphone to register as +welcomeTo=Welcome to \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java index 5ff5ea83c96..2c153c4f619 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.racelogtracking; +import java.util.Locale; import java.util.Set; import com.sap.sailing.domain.abstractlog.race.RaceLog; @@ -102,6 +103,6 @@ public interface RaceLogTrackingAdapter { /** * Invite all competitors known in the leaderboard for tracking via the Tracking App by sending out emails. */ - void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard, String serverUrlWithoutTrailingSlash) - throws MailException; + void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard, + String serverUrlWithoutTrailingSlash, Locale locale) throws MailException; } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index 331dbb248fe..bb4164638b1 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -356,7 +357,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { @Override public void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard, - String serverUrlWithoutTrailingSlash) throws MailException { + String serverUrlWithoutTrailingSlash, Locale locale) throws MailException { //TODO check bug 2587 Iterable competitors = leaderboard.getAllCompetitors(); @@ -368,25 +369,27 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { if (toAddress != null) { String leaderboardName = leaderboard.getName(); String competitorName = competitor.getName(); - - //TODO i18n + String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash, event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING, competitor.getId().toString()); - String subject = "Tracking invitation for " + competitorName; + String subject = String.format("%s %s", + RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "trackingInvitationFor"), competitorName); // taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm BodyPart messageTextPart = new MimeBodyPart(); - String htmlText = String.format("

    Welcome to %s

    " - + "

    Scan the following QRCode or visit the URL using your smartphone to register as %s

    " + String htmlText = String.format("

    %s %s

    " + "

    %s %s

    " + "" - + "%s", leaderboardName, competitorName, url, url); + + "%s", + RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "welcomeTo"), leaderboardName, + RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), competitorName, + url, url); try { messageTextPart.setContent(htmlText, "text/html"); BodyPart messageImagePart = new MimeBodyPart(); - InputStream imageIs = QRCodeGenerationUtil.create(url, 64); + InputStream imageIs = QRCodeGenerationUtil.create(url, 128); DataSource imageDs = new ByteArrayDataSource(imageIs, "image/png"); messageImagePart.setDataHandler(new DataHandler(imageDs)); messageImagePart.setHeader("Content-ID", ""); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingI18n.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingI18n.java new file mode 100644 index 00000000000..0a3fa3a6876 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingI18n.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sse.i18n.ResourceBundleStringMessages; +import com.sap.sse.i18n.impl.ResourceBundleStringMessagesImpl; + +public class RaceLogTrackingI18n { + private static final String RESOURCE_BASE_NAME = "stringmessages/TrackingStringMessages"; + + public static final ResourceBundleStringMessages STRING_MESSAGES = new ResourceBundleStringMessagesImpl( + RESOURCE_BASE_NAME, RaceLogTrackingI18n.class.getClassLoader()); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index d275f02586f..02b9c482fbe 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -577,6 +577,6 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS List getDeviceMappingsFromLogHierarchy(String leaderboardName, String raceColumnName, String fleetName) throws TransformationException; - void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, String leaderboardName) - throws MailException; + void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, + String leaderboardName, String localeInfo) throws MailException; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 0eecf745f99..9517dbae100 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -689,5 +689,5 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM void getActiveFileStorageServiceName(AsyncCallback callback); void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, - String leaderboardName, AsyncCallback callback); + String leaderboardName, String localeInfo, AsyncCallback callback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 2def8141b39..586c84a5dc6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -5386,13 +5386,16 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } return getService().getFileStorageManagementService().getFileStorageService(name); } + + private Locale getLocale(String localeInfoName) { + return ResourceBundleStringMessages.Util.getLocaleFor(localeInfoName); + } @Override public FileStorageServiceDTO[] getAvailableFileStorageServices(String localeInfoName) { - Locale locale = ResourceBundleStringMessages.Util.getLocaleFor(localeInfoName); List serviceDtos = new ArrayList<>(); for (FileStorageService s : getService().getFileStorageManagementService().getAvailableFileStorageServices()) { - serviceDtos.add(FileStorageServiceDTOUtils.convert(s, locale)); + serviceDtos.add(FileStorageServiceDTOUtils.convert(s, getLocale(localeInfoName))); } return serviceDtos.toArray(new FileStorageServiceDTO[0]); } @@ -5411,14 +5414,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public FileStorageServicePropertyErrorsDTO testFileStorageServiceProperties(String serviceName, String localeInfoName) { - Locale locale = ResourceBundleStringMessages.Util.getLocaleFor(localeInfoName); try { FileStorageService service = getFileStorageService(serviceName); if (service != null) { service.testProperties(); } } catch (InvalidPropertiesException e) { - return FileStorageServiceDTOUtils.convert(e, locale); + return FileStorageServiceDTOUtils.convert(e, getLocale(localeInfoName)); } return null; } @@ -5439,9 +5441,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, - String leaderboardName) throws MailException { + String leaderboardName, String localeInfoName) throws MailException { Event event = getService().getEvent(eventId); Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); - getRaceLogTrackingAdapter().inviteCompetitorsForTrackingViaEmail(event, leaderboard, serverUrlWithoutTrailingSlash); + getRaceLogTrackingAdapter().inviteCompetitorsForTrackingViaEmail(event, leaderboard, serverUrlWithoutTrailingSlash, + getLocale(localeInfoName)); } } From 26b8c5938240b478a74923e794db9a38b8aac047 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Mon, 9 Feb 2015 23:03:51 +0100 Subject: [PATCH 048/264] removed obsolete code dealing with mail properties from SailingService --- .../gwt/ui/server/SailingServiceImpl.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 586c84a5dc6..ce3ee76c87b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1,7 +1,5 @@ package com.sap.sailing.gwt.ui.server; -import java.io.File; -import java.io.FileReader; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; @@ -31,7 +29,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; -import java.util.Properties; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; @@ -503,8 +500,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final QuickRanksLiveCache quickRanksLiveCache; - private final Properties mailProperties; - public SailingServiceImpl() { BundleContext context = Activator.getDefault(); Activator activator = Activator.getInstance(); @@ -559,20 +554,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S /* maximumPoolSize */ THREAD_POOL_SIZE, /* keepAliveTime */ 60, TimeUnit.SECONDS, /* workQueue */ new LinkedBlockingQueue()); - - final String jettyHome = System.getProperty("jetty.home", "configuration"); - final File propertiesDir = new File(jettyHome).getParentFile(); - File propertiesfile = new File(propertiesDir, "security.properties"); - mailProperties = new Properties(); - try { - mailProperties.load(new FileReader(propertiesfile)); - } catch (IOException ioe) { - try { - logger.log(Level.SEVERE, "Couldn't read security properties from "+propertiesfile.getCanonicalPath(), ioe); - } catch (Exception e) { - logger.log(Level.SEVERE, "Couldn't read security properties and could net fetch canonical path"); - } - } } /** From 767652381b1c11834ed34a20f352a0eceba4b1b1 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 00:48:19 +0100 Subject: [PATCH 049/264] add GWT-context-sensitive URLEncoder --- .../tracking/DeviceMappingConstants.java | 31 +++++++------------ .../src/com/sap/sse/common/URLEncoder.java | 30 ++++++++++++++++++ .../gwt/client/controls}/GenericListBox.java | 0 3 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 java/com.sap.sse.common/src/com/sap/sse/common/URLEncoder.java rename java/{com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client => com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls}/GenericListBox.java (100%) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java index 7cb7ee30369..88a537cf67f 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.common.racelog.tracking; -import com.google.gwt.http.client.URL; import com.sap.sailing.domain.common.racelog.RaceLogServletConstants; +import com.sap.sse.common.URLEncoder; /** * Shared between GWT and Android. Used for creating and deciphering the URL encoded by the QRCode, which gives the @@ -40,20 +40,14 @@ public class DeviceMappingConstants { @Deprecated static final String APK_PATH = "/apps/com.sap.sailing.android.tracking.app.apk"; - - public static String encode(String value) { - //pathSegment instead of queryString, so that ' ' is encoded as '%20' - return URL.encodePathSegment(value); - } - + public static String getDeviceMappingForRegattaLogUrl(String serverUrlWithoutTrailingSlash, String eventId, String leaderboardName, String mappedItemType, String mappedItemId) { - return serverUrlWithoutTrailingSlash + URL_BASE + "?" - + URL_EVENT_ID + "=" + encode(eventId) + "&" - + URL_LEADERBOARD_NAME + "=" + encode(leaderboardName) - + "&" + mappedItemType + "=" + encode(mappedItemId); + return URLEncoder.encode(serverUrlWithoutTrailingSlash + URL_BASE + "?" + URL_EVENT_ID + "=" + eventId + "&" + + URL_LEADERBOARD_NAME + "=" + leaderboardName + "&" + mappedItemType + "=" + + mappedItemId); } - + @Deprecated public static String getDeviceMappingForRaceLogUrl(String serverUrlWithoutTrailingSlash, String leaderboardName, String raceColumnName, String fleetName, String mappedItemType, String mappedItemId, long fromMillis, @@ -61,12 +55,11 @@ public class DeviceMappingConstants { if (fromMillis > toMillis) { throw new QRCodeURLCreationException("from can't lie after to"); } - return serverUrlWithoutTrailingSlash + DeviceMappingConstants.APK_PATH + "?" - + RaceLogServletConstants.PARAMS_LEADERBOARD_NAME + "=" + encode(leaderboardName) + "&" - + RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME + "=" + encode(raceColumnName) - + "&" + RaceLogServletConstants.PARAMS_RACE_FLEET_NAME + "=" + encode(fleetName) - + "&" + mappedItemType + "=" + encode(mappedItemId) - + "&" + DeviceMappingConstants.URL_FROM_MILLIS + "=" + fromMillis - + "&" + DeviceMappingConstants.URL_TO_MILLIS + "=" + toMillis; + return URLEncoder.encode(serverUrlWithoutTrailingSlash + DeviceMappingConstants.APK_PATH + "?" + + RaceLogServletConstants.PARAMS_LEADERBOARD_NAME + "=" + leaderboardName + "&" + + RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME + "=" + raceColumnName + "&" + + RaceLogServletConstants.PARAMS_RACE_FLEET_NAME + "=" + fleetName + "&" + mappedItemType + "=" + + mappedItemId + "&" + DeviceMappingConstants.URL_FROM_MILLIS + "=" + fromMillis + "&" + + DeviceMappingConstants.URL_TO_MILLIS + "=" + toMillis); } } diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/URLEncoder.java b/java/com.sap.sse.common/src/com/sap/sse/common/URLEncoder.java new file mode 100644 index 00000000000..19384a77d13 --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/URLEncoder.java @@ -0,0 +1,30 @@ +package com.sap.sse.common; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +public class URLEncoder { + /** + * Use different encoding strategies, depending on whether running in GWT or server context. + */ + public static String encode(String decodedUrlString) { + try { + Class.forName("com.google.gwt.http.client.URL"); + // GWT context + return com.google.gwt.http.client.URL.encode(decodedUrlString); + } catch (ClassNotFoundException e) { + // non-GWT context (URL/URI should be fully available) + try { + URL url = new URL(decodedUrlString); + URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), + url.getQuery(), url.getRef()); + return uri.toString(); + } catch (MalformedURLException | URISyntaxException f) { + f.printStackTrace(); + return null; + } + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/GenericListBox.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/GenericListBox.java similarity index 100% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/GenericListBox.java rename to java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/GenericListBox.java From 639eba52262f8a348a8601d585701cad6754a96e Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 00:49:24 +0100 Subject: [PATCH 050/264] improve competitor invitation UI --- .../RaceLogTrackingAdapter.java | 4 +- .../impl/RaceLogTrackingAdapterImpl.java | 6 +- .../AddDeviceMappingToRegattaLogDialog.java | 4 +- .../gwt/ui/adminconsole/CompetitorPanel.java | 157 ++++++++++++++---- .../sailing/gwt/ui/client/SailingService.java | 4 +- .../gwt/ui/client/SailingServiceAsync.java | 4 +- .../sailing/gwt/ui/client/StringMessages.java | 1 + .../gwt/ui/client/StringMessages.properties | 3 +- .../ui/client/StringMessages_de.properties | 3 +- .../ui/client/StringMessages_ru.properties | 3 +- .../gwt/ui/server/SailingServiceImpl.java | 12 +- .../gwt/client/controls/GenericListBox.java | 2 +- .../gwt/client/dialog/DataEntryDialog.java | 17 ++ 13 files changed, 166 insertions(+), 54 deletions(-) diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java index 2c153c4f619..b6efc960f89 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java @@ -101,8 +101,8 @@ public interface RaceLogTrackingAdapter { void registerCompetitors(RacingEventService service, RegattaLog regattaLog, Set competitors); /** - * Invite all competitors known in the leaderboard for tracking via the Tracking App by sending out emails. + * Invite competitors for tracking via the Tracking App by sending out emails. */ void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard, - String serverUrlWithoutTrailingSlash, Locale locale) throws MailException; + String serverUrlWithoutTrailingSlash, Set competitors, Locale locale) throws MailException; } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index bb4164638b1..e4a4fff55b4 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -357,11 +357,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { @Override public void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard, - String serverUrlWithoutTrailingSlash, Locale locale) throws MailException { - //TODO check bug 2587 - Iterable competitors = leaderboard.getAllCompetitors(); - - + String serverUrlWithoutTrailingSlash, Set competitors, Locale locale) throws MailException { StringBuilder occuredExceptions = new StringBuilder(); for (Competitor competitor : competitors){ diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java index 93d3cfd2376..db1ca541340 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java @@ -17,11 +17,11 @@ import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException import com.sap.sailing.gwt.ui.adminconsole.ItemToMapToDeviceSelectionPanel.SelectionChangedHandler; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.common.client.GenericListBox; -import com.sap.sailing.gwt.ui.common.client.GenericListBox.ValueBuilder; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.controls.GenericListBox; +import com.sap.sse.gwt.client.controls.GenericListBox.ValueBuilder; public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog { private DeviceMappingQRCodeWidget qrWidget; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java index 8c4483c0758..54f8369f8ad 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java @@ -1,19 +1,24 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.UUID; +import java.util.Set; import com.google.gwt.cell.client.FieldUpdater; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; @@ -21,8 +26,13 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTOImpl; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.controls.GenericListBox; +import com.sap.sse.gwt.client.controls.GenericListBox.ValueBuilder; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; /** @@ -86,32 +96,46 @@ public class CompetitorPanel extends SimplePanel { } }); buttonPanel.add(addCompetitorButton); - - final Button inviteCompetitorsButton = new Button(stringMessages.inviteCompetitors()); - inviteCompetitorsButton.addClickHandler(new ClickHandler() { + + Button selectAllButton = new Button(stringMessages.selectAll()); + selectAllButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - Iterable competitors = competitorSelectionModel.getSelectedSet(); - boolean emailProvidedForAll = isEmailProvidedForAll(competitors); - - if (emailProvidedForAll) { - openChooseEventDialogAndSendMails(competitors); - } else { - Window.alert(stringMessages.notAllCompetitorsProvideEmail()); + for (CompetitorDTO c : competitorTable.getDataProvider().getList()) { + competitorSelectionModel.setSelected(c, true); } } + }); + buttonPanel.add(selectAllButton); - private boolean isEmailProvidedForAll(Iterable allCompetitors) { - for (CompetitorDTO competitor : allCompetitors) { - if (!competitor.hasEmail()) { - return false; + //only if this competitor panel is connected to a leaderboard, we want to enable invitations + if (leaderboardName != null) { + final Button inviteCompetitorsButton = new Button(stringMessages.inviteCompetitors()); + inviteCompetitorsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + Set competitors = competitorSelectionModel.getSelectedSet(); + boolean emailProvidedForAll = isEmailProvidedForAll(competitors); + + if (emailProvidedForAll) { + openChooseEventDialogAndSendMails(competitors); + } else { + Window.alert(stringMessages.notAllCompetitorsProvideEmail()); } } - return true; - } - }); - buttonPanel.add(inviteCompetitorsButton); + private boolean isEmailProvidedForAll(Iterable allCompetitors) { + for (CompetitorDTO competitor : allCompetitors) { + if (!competitor.hasEmail()) { + return false; + } + } + + return true; + } + }); + buttonPanel.add(inviteCompetitorsButton); + } competitorsPanel.add(buttonPanel); @@ -188,18 +212,86 @@ public class CompetitorPanel extends SimplePanel { } }).show(); } + + private class SelectEventAndHostnameDialog extends DataEntryDialog> { + private GenericListBox events; + private TextBox serverUrl; + + public SelectEventAndHostnameDialog(DialogCallback> callback) { + super(stringMessages.selectEventForInvitation(), null, stringMessages.inviteCompetitors(), stringMessages.cancel(), + new Validator>() { + @Override + public String getErrorMessage(Pair valueToValidate) { + if (valueToValidate.getA() == null) { + return stringMessages.pleaseSelectAnEvent(); + } else if (valueToValidate.getB() == null || valueToValidate.getB().isEmpty()) { + return stringMessages.pleaseEnterA(stringMessages.hostname()); + } + return null; + } + }, false, callback); + } - private void openChooseEventDialogAndSendMails(final Iterable competitors) { - EventListCompositeDialog dialog = new EventListCompositeDialog(sailingService, stringMessages, errorReporter, - new DialogCallback>() { + @Override + protected Pair getResult() { + EventDTO event = events.getValue(); + String serverUrlString = serverUrl.getValue(); + if (serverUrlString.endsWith("/")) { + serverUrlString = serverUrlString.substring(0, serverUrlString.length() - 1); + } + return new Pair<>(event, serverUrlString); + } + + @Override + protected Widget getAdditionalWidget() { + Grid grid = new Grid(2, 2); + grid.setWidget(0, 0, createLabel(stringMessages.hostname())); + serverUrl = createTextBox(Window.Location.getProtocol() + "//" + Window.Location.getHost()); + grid.setWidget(0, 1, serverUrl); + + grid.setWidget(1, 0, createLabel(stringMessages.event())); + events = createGenericListBox(new ValueBuilder() { + @Override + public String getValue(EventDTO item) { + if (item == null) { + return ""; + } + return item.getName(); + } + }, false); + grid.setWidget(1, 1, events); + + events.addItem((EventDTO) null); + sailingService.getEventsForLeaderboard(leaderboardName, new AsyncCallback>() { + @Override + public void onSuccess(Collection result) { + events.addItems(result); + } - @Override - public void ok(List eventsToInviteTo) { - sailingService.sendInvitationEmailToCompetitors(competitors, leaderboardName, eventsToInviteTo, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load events: " + caught.getMessage()); + } + }); + return grid; + } + } + + private String getLocaleInfo() { + return LocaleInfo.getCurrentLocale().getLocaleName(); + } + + private void openChooseEventDialogAndSendMails(final Set competitors) { + new SelectEventAndHostnameDialog(new DialogCallback>() { + + @Override + public void ok(Pair result) { + sailingService.inviteCompetitorsForTrackingViaEmail(result.getB(), result.getA(), leaderboardName, + competitors, getLocaleInfo(), new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.alert(stringMessages.sendingMailsFailed()+caught.getMessage()); + Window.alert(stringMessages.sendingMailsFailed() + caught.getMessage()); } @Override @@ -207,14 +299,13 @@ public class CompetitorPanel extends SimplePanel { Window.alert(stringMessages.sendingMailsSuccessfull()); } }); + } - } - - @Override - public void cancel() { - } - }); - dialog.show(); + @Override + public void cancel() { + + } + }).show(); } public void refreshCompetitorList() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 02b9c482fbe..0720861da09 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -577,6 +577,6 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS List getDeviceMappingsFromLogHierarchy(String leaderboardName, String raceColumnName, String fleetName) throws TransformationException; - void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, - String leaderboardName, String localeInfo) throws MailException; + void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, EventDTO event, + String leaderboardName, Set competitors, String localeInfo) throws MailException; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 9517dbae100..e95502447d5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -688,6 +688,6 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM void getActiveFileStorageServiceName(AsyncCallback callback); - void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, - String leaderboardName, String localeInfo, AsyncCallback callback); + void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, EventDTO event, + String leaderboardName, Set competitors, String localeInfo, AsyncCallback callback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 18979f74c92..09ce5990a2e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1143,4 +1143,5 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String notAllCompetitorsProvideEmail(); String sendingMailsFailed(); String sendingMailsSuccessfull(); + String selectEventForInvitation(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index a1a537f6487..610dc6953d7 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1124,4 +1124,5 @@ pleaseEnterAnEmail=Please enter an E-Mail. inviteCompetitors=Invite competitors notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail sendingMailsFailed=Sending Mails failed -sendingMailsSuccessfull=Successfully sent E-Mails \ No newline at end of file +sendingMailsSuccessfull=Successfully sent E-Mails +selectEventForInvitation=Select Event for Invitation \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 6c28a4ae555..c571e2ca304 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1114,4 +1114,5 @@ pleaseEnterAnEmail=Bitte geben Sie eine E-Mail ein. inviteCompetitors=Teilnehmer einladen notAllCompetitorsProvideEmail=Nicht alle Teilnehmer haben eine E-Mail angegeben sendingMailsFailed=Versenden der E-Mails fehlgeschlagen -sendingMailsSuccessfull=Versenden der E-Mails war erfolgreich! \ No newline at end of file +sendingMailsSuccessfull=Versenden der E-Mails war erfolgreich! +selectEventForInvitation=Event für Einladung auswählen \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 2ed10f58d75..ba33651f4ec 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1062,4 +1062,5 @@ pleaseEnterAnEmail=ПожалуйÑ�та, введите Ñ�маль inviteCompetitors=приглашать УчаÑ�тники notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail sendingMailsFailed=Sending Mails failed -sendingMailsSuccessfull=Successfully sent E-Mails \ No newline at end of file +sendingMailsSuccessfull=Successfully sent E-Mails +selectEventForInvitation=Select Event for Invitation \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index ce3ee76c87b..9e05c87907e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -5421,11 +5421,15 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, UUID eventId, - String leaderboardName, String localeInfoName) throws MailException { - Event event = getService().getEvent(eventId); + public void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, EventDTO eventDto, + String leaderboardName, Set competitorDtos, String localeInfoName) throws MailException { + Event event = getService().getEvent(eventDto.id); + Set competitors = new HashSet<>(); + for (CompetitorDTO c : competitorDtos) { + competitors.add(getCompetitor(c)); + } Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); getRaceLogTrackingAdapter().inviteCompetitorsForTrackingViaEmail(event, leaderboard, serverUrlWithoutTrailingSlash, - getLocale(localeInfoName)); + competitors, getLocale(localeInfoName)); } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/GenericListBox.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/GenericListBox.java index 86925fdce37..621b5d494a3 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/GenericListBox.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/GenericListBox.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.common.client; +package com.sap.sse.gwt.client.controls; import java.util.ArrayList; import java.util.Collection; diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DataEntryDialog.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DataEntryDialog.java index fdc9f4bb7fe..33d5e0caa06 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DataEntryDialog.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/dialog/DataEntryDialog.java @@ -30,6 +30,8 @@ import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.datepicker.client.DateBox; +import com.sap.sse.gwt.client.controls.GenericListBox; +import com.sap.sse.gwt.client.controls.GenericListBox.ValueBuilder; import com.sap.sse.gwt.client.controls.IntegerBox; /** @@ -417,6 +419,21 @@ public abstract class DataEntryDialog { result.setWordWrap(false); return result; } + + public GenericListBox createGenericListBox(ValueBuilder valueBuilder, + boolean isMultipleSelect) { + GenericListBox result = new GenericListBox<>(valueBuilder); + result.setMultipleSelect(isMultipleSelect); + result.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + validate(); + } + }); + DialogUtils.linkEnterToButton(getOkButton(), result); + DialogUtils.linkEscapeToButton(getCancelButton(), result); + return result; + } public ListBox createListBox(boolean isMultipleSelect) { ListBox result = new ListBox(); From 04608c2ae718f9552070f8dfda8649ea908aa69c Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 00:49:53 +0100 Subject: [PATCH 051/264] add email to constructor of CompetitorImpl and fix bug (competitor email not loading on startup) --- .../analyzing/test/MultiLogAnalyzerTest.java | 2 +- .../test/DeviceMappingFinderTest.java | 4 +- .../domain/base/CompetitorChangeListener.java | 2 + .../domain/base/impl/CompetitorImpl.java | 14 +++-- .../impl/TransientCompetitorStoreImpl.java | 6 +-- .../domain/test/AbstractLeaderboardTest.java | 20 +++---- .../test/LeaderboardCourseChangeTest.java | 4 +- .../domain/test/OfflineSerializationTest.java | 10 ++-- .../domain/test/StoredTrackBasedTest.java | 2 +- .../com/sap/sailing/domain/test/TackTest.java | 10 ++-- .../sailing/domain/test/TrackBasedTest.java | 10 ++-- .../com/sap/sailing/domain/test/WindTest.java | 10 ++-- .../AbstractMockedRaceMarkPassingTest.java | 6 +-- .../test/PolarSheetGenerationServiceTest.java | 4 +- .../StoreAndLoadDeviceIdentifierTest.java | 2 +- ...estStoringAndLoadingEventsAndRegattas.java | 2 +- ...StoringAndRetrievingLeaderboardGroups.java | 54 +++++++++---------- .../TestStoringAndRetrievingLeaderboards.java | 18 +++---- .../polars/test/PolarSheetGenerationTest.java | 4 +- .../gateway/test/AbstractJsonExportTest.java | 10 ++-- .../test/RaceLogSerializationTest.java | 4 +- .../test/jaxrs/AbstractJaxRsApiTest.java | 10 ++-- .../server/test/MasterDataImportTest.java | 48 ++++++++--------- .../app/data/OfflineDataManager.java | 10 ++-- 24 files changed, 137 insertions(+), 129 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/MultiLogAnalyzerTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/MultiLogAnalyzerTest.java index 60dbfaa4d6d..fe618620b4c 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/MultiLogAnalyzerTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/MultiLogAnalyzerTest.java @@ -52,7 +52,7 @@ public class MultiLogAnalyzerTest { private AbstractLogEventAuthor author; private static Competitor createCompetitor(int n) { - return new CompetitorImpl(n, n + "", null, null, null); + return new CompetitorImpl(n, n + "", null, null, null, null); } private static DeviceIdentifier createDevice() { diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java index b564043ec86..8eb28322665 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/tracking/analyzing/test/DeviceMappingFinderTest.java @@ -22,8 +22,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest { - private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null); - private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null); + private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null, null); + private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null, null); private final DeviceIdentifier device = new SmartphoneImeiIdentifier("imei"); private int time = 0; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java index 35347c8e881..a72bbbb6721 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorChangeListener.java @@ -6,4 +6,6 @@ public interface CompetitorChangeListener extends BoatChangeListener, Nationalit void colorChanged(Color oldColor, Color newColor); void nameChanged(String oldName, String newName); + + void emailChanged(String oldEmail, String newEmail); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java index 710d6fadc62..b9ffafa4c58 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java @@ -22,12 +22,13 @@ public class CompetitorImpl implements DynamicCompetitor { private transient Set listeners; private String email; - public CompetitorImpl(Serializable id, String name, Color color, DynamicTeam team, DynamicBoat boat) { + public CompetitorImpl(Serializable id, String name, Color color, String email, DynamicTeam team, DynamicBoat boat) { this.id = id; this.name = name; this.team = team; this.boat = boat; this.color = color; + this.email = email; this.listeners = new HashSet(); } @@ -120,9 +121,14 @@ public class CompetitorImpl implements DynamicCompetitor { return email; } - public void setEmail(String eMail) { - //TODO: add E-Mail verification from SecurityServiceImpl - this.email = eMail; + public void setEmail(String newEmail) { + final String oldEmail = this.email; + if (!Util.equalsWithNull(oldEmail, newEmail)) { + this.email = newEmail; + for (CompetitorChangeListener listener : getListeners()) { + listener.emailChanged(oldEmail, newEmail); + } + } } public boolean hasEmail(){ diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java index 5449d5a2cdf..7c9f8395f45 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java @@ -68,8 +68,8 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab listeners.remove(listener); } - private Competitor createCompetitor(Serializable id, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { - Competitor result = new CompetitorImpl(id, name, displayColor, team, boat); + private Competitor createCompetitor(Serializable id, String name, Color displayColor, String email, DynamicTeam team, DynamicBoat boat) { + Competitor result = new CompetitorImpl(id, name, displayColor, email, team, boat); addNewCompetitor(id, result); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "Created competitor "+name+" with ID "+id, new Exception("Here is where it happened")); @@ -100,7 +100,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab try { result = getExistingCompetitorById(competitorId); // try again, now while holding the write lock if (result == null) { - result = createCompetitor(competitorId, name, displayColor, team, boat); + result = createCompetitor(competitorId, name, displayColor, email, team, boat); } } finally { LockUtil.unlockAfterWrite(lock); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java index 35c0c694983..65e0817f4ee 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java @@ -15,11 +15,11 @@ import com.sap.sse.common.Color; public abstract class AbstractLeaderboardTest { public static CompetitorImpl createCompetitor(String competitorName) { - return new CompetitorImpl(competitorName, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + return new CompetitorImpl(competitorName, competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), /* sailID */ null)); } @@ -33,11 +33,11 @@ public abstract class AbstractLeaderboardTest { } public static CompetitorImpl createCompetitor(String competitorName, Serializable id) { - return new CompetitorImpl(id, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + return new CompetitorImpl(id, competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), /* sailID */ null)); } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java index c6e4e4711ab..b05c4afee5e 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java @@ -157,8 +157,8 @@ public class LeaderboardCourseChangeTest { Set competitors = new HashSet<>(); DynamicBoat mockedBoat = mock(DynamicBoat.class); when(mockedBoat.getBoatClass()).thenReturn(boatClass); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "TestCompetitor", Color.BLACK, mock(DynamicTeam.class), - mockedBoat)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "TestCompetitor", Color.BLACK, null, + mock(DynamicTeam.class), mockedBoat)); return competitors; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java index 1e34a56762c..f6d164910e6 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java @@ -188,11 +188,11 @@ public class OfflineSerializationTest extends AbstractSerializationTest { DomainFactory senderDomainFactory = new DomainFactoryImpl(); DomainFactory receiverDomainFactory = new DomainFactoryImpl(); String competitorName = "Tina Maximiliane Lutz"; - Competitor sendersCompetitor1 = new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, senderDomainFactory.getOrCreateNationality("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", senderDomainFactory.getOrCreateNationality("GER"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + Competitor sendersCompetitor1 = new CompetitorImpl(123, competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, senderDomainFactory.getOrCreateNationality("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", senderDomainFactory.getOrCreateNationality("GER"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", senderDomainFactory.getOrCreateBoatClass("470", /* typicallyStartsUpwind */ true), "GER 61")); Competitor receiversCompetitor1 = cloneBySerialization(sendersCompetitor1, receiverDomainFactory); Competitor receiversSecondCopyOfCompetitor1 = cloneBySerialization(sendersCompetitor1, receiverDomainFactory); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java index 9b5bc2dacf6..1ca4fa9f623 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java @@ -117,7 +117,7 @@ public abstract class StoredTrackBasedTest extends TrackBasedTest { for (String competitorName : getCompetitorNamesOfStoredTracks(KIELER_WOCHE)) { DynamicPerson p = new PersonImpl(competitorName, /* nationality */ null, /* dateOfBirth */ null, /* description */ null); DynamicTeam t = new TeamImpl(competitorName, Collections.singleton(p), /* coach */ null); - Competitor c = new CompetitorImpl(competitorName, competitorName, Color.RED, t, new BoatImpl(competitorName, + Competitor c = new CompetitorImpl(competitorName, competitorName, Color.RED, null, t, new BoatImpl(competitorName, new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); DynamicGPSFixTrack track = readTrack(c, KIELER_WOCHE); if (track != null) { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java index 414ea94a80f..fc0c6037871 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java @@ -34,11 +34,11 @@ public class TackTest extends StoredTrackBasedTestWithTrackedRace { @Override @Before public void setUp() { - competitor = new CompetitorImpl(123, "Wolfgang Hunger", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Wolfgang Hunger", new NationalityImpl("GER"), - /* dateOfBirth */null, "This is famous Wolfgang Hunger")), new PersonImpl("Rigo van Maas", - new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Wolfgang Hunger's boat", + competitor = new CompetitorImpl(123, "Wolfgang Hunger", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("Wolfgang Hunger", new NationalityImpl("GER"), + /* dateOfBirth */null, "This is famous Wolfgang Hunger")), new PersonImpl("Rigo van Maas", + new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); setTrackedRace(createTestTrackedRace("Kieler Woche", "505 Race 2", "505", Collections.singleton(competitor), new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()))); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java index 99d8ab7bfe9..faeaca23b4e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java @@ -60,11 +60,11 @@ public abstract class TrackBasedTest { } public static CompetitorImpl createCompetitor(String competitorName) { - return new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas", - new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + return new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas", + new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", new BoatClassImpl("505", /* typicallyStartsUpwind */true), null)); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java index 73397e43197..64368e6e02e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java @@ -270,11 +270,11 @@ public class WindTest { Waypoint w1 = domainFactory.createWaypoint(startFinish, /*passingInstruction*/ null); Waypoint w2 = domainFactory.createWaypoint(top, /*passingInstruction*/ null); Waypoint w3 = domainFactory.createWaypoint(startFinish, /*passingInstruction*/ null); - Competitor competitor = new CompetitorImpl(123, "Test Competitor", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Test Competitor", new NationalityImpl("GER"), - /* dateOfBirth */null, "This is famous " + "Test Competitor")), new PersonImpl("Rigo van Maas", - new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Test Competitor" + "'s boat", + Competitor competitor = new CompetitorImpl(123, "Test Competitor", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("Test Competitor", new NationalityImpl("GER"), + /* dateOfBirth */null, "This is famous " + "Test Competitor")), new PersonImpl("Rigo van Maas", + new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Test Competitor" + "'s boat", new BoatClassImpl("505", /* typicallyStartsUpwind */true), null)); final BoatClass boatClass = domainFactory.getOrCreateBoatClass("ESS40"); DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(new DynamicTrackedRegattaImpl( diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java index 4e636675bbd..30ce75475d8 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java @@ -45,9 +45,9 @@ import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; public class AbstractMockedRaceMarkPassingTest { - protected Competitor bob = new CompetitorImpl("Bob", "Bob", null, null, null); - protected Competitor joe = new CompetitorImpl("Joe", "Joe", null, null, null); - protected Competitor mike = new CompetitorImpl("Mike", "Mike", null, null, null); + protected Competitor bob = new CompetitorImpl("Bob", "Bob", null, null, null, null); + protected Competitor joe = new CompetitorImpl("Joe", "Joe", null, null, null, null); + protected Competitor mike = new CompetitorImpl("Mike", "Mike", null, null, null, null); protected Mark m = new MarkImpl("black"); protected Mark m2 = new MarkImpl("white 1"); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java index e9311746a13..f0fc52e1ea1 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java @@ -163,8 +163,8 @@ public class PolarSheetGenerationServiceTest { waypoints.add(new WaypointImpl(null)); waypoints.add(new WaypointImpl(null)); ArrayList competitors = new ArrayList(); - Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, new TeamImpl("SAP", null, null), - new BoatImpl("Schnelle Forelle", forelle, "GER000")); + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, null, + new TeamImpl("SAP", null, null), new BoatImpl("Schnelle Forelle", forelle, "GER000")); competitors.add(competitor); RaceDefinition race = new RaceDefinitionImpl("Forelle1", new CourseImpl("ForelleCourse", waypoints), forelle, competitors); return race; diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadDeviceIdentifierTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadDeviceIdentifierTest.java index 08ece8cbcc0..dad04664a00 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadDeviceIdentifierTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadDeviceIdentifierTest.java @@ -82,7 +82,7 @@ public class StoreAndLoadDeviceIdentifierTest extends AbstractMongoDBTest { private DeviceIdentifier storeAndLoad(DeviceIdentifier device, TypeBasedServiceFinderFactory forStoring, TypeBasedServiceFinderFactory forLoading) { createFactories(forStoring); - Competitor c = new CompetitorImpl("a", "a", null, null, null); + Competitor c = new CompetitorImpl("a", "a", null, null, null, null); raceLog.add(RaceLogEventFactory.INSTANCE.createDeviceCompetitorMappingEvent(now(), author, device, c, 0, now(), now())); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java index 720b868c6db..ff13f587b82 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java @@ -627,7 +627,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null); List competitors = new ArrayList(); - competitors.add(new CompetitorImpl("Axel", "Axel Uhl", Color.RED, null, null)); + competitors.add(new CompetitorImpl("Axel", "Axel Uhl", Color.RED, null, null, null)); Iterable waypoints = Collections.emptyList(); Course course = new CourseImpl("Course", waypoints); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java index 63f90f5e010..538815fa923 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java @@ -113,15 +113,15 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe @Test public void testStoringAndRetrievingLeaderboardGroupWithOverallLeaderboardWithScoreCorrection() throws NoWindException { RacingEventService racingEventService = new RacingEventServiceImpl(); - Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); - Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is Patty, the coach")), - new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is Patty, the coach")), new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); final String raceColumnName1 = "My First Race 1"; MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true); raceWithTwoCompetitors.addCompetitor(hasso); @@ -169,15 +169,15 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe @Test public void testStoringAndRetrievingLeaderboardGroupWithSuppressedCompetitorsInOverallLeaderboard() { - Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); - Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is Patty, the coach")), - new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is Patty, the coach")), new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); final String raceColumnName1 = "My First Race 1"; MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true); raceWithTwoCompetitors.addCompetitor(hasso); @@ -225,15 +225,15 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe @Test public void testStoringAndRetrievingLeaderboardGroupWithTwoSuppressedCompetitorsInOverallLeaderboard() { - Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); - Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is Patty, the coach")), - new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor wolfgang = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is Patty, the coach")), new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); final String raceColumnName1 = "My First Race 1"; MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true); raceWithTwoCompetitors.addCompetitor(hasso); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java index 5d849ea0eb6..0eae97a4c52 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java @@ -76,11 +76,11 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { FlexibleLeaderboardImpl leaderboard = new FlexibleLeaderboardImpl(leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces), new LowPoint(), null); Competitor wolfgang = createCompetitor(); - Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is Patty, the coach")), - new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor hasso = new CompetitorImpl(234, "Hasso Plattner", Color.RED, null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl("Hasso Plattner", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Hasso Plattner")), new PersonImpl("Lutz Patrunky", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is Patty, the coach")), new BoatImpl("Dr. Hasso Plattner's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); final String raceColumnName1 = "My First Race 1"; MockedTrackedRaceWithFixedRankAndManyCompetitors raceWithTwoCompetitors = new MockedTrackedRaceWithFixedRankAndManyCompetitors(wolfgang, /* rank */ 1, /* started */ true); raceWithTwoCompetitors.addCompetitor(hasso); @@ -377,10 +377,10 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { } private Competitor createCompetitor() { - Competitor competitor = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); + Competitor competitor = new CompetitorImpl(123, "$$$Dr. Wolfgang+Hunger$$$", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("$$$Dr. Wolfgang+Hunger$$$", new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous Dr. Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */ null, "This is Rigo, the coach")), new BoatImpl("Dr. Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); return competitor; } diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java index 31622df7ab3..42b953c8aa9 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java @@ -91,8 +91,8 @@ public class PolarSheetGenerationTest { settings, executor); BoatClass forelle = new BoatClassImpl("Forelle", true); - Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, new TeamImpl("SAP", null, null), - new BoatImpl("Schnelle Forelle", forelle, "GER000")); + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, null, + new TeamImpl("SAP", null, null), new BoatImpl("Schnelle Forelle", forelle, "GER000")); PolarFixAggregationWorker task = new PolarFixAggregationWorker(race, resultContainer, startTime, endTime, diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java index 9cbc93bc25a..bc6cf71f321 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/AbstractJsonExportTest.java @@ -89,11 +89,11 @@ public abstract class AbstractJsonExportTest { BoatClass boatClass = new BoatClassImpl("505", /* typicallyStartsUpwind */ true); for (int i = 1; i <= numberOfCompetitorsToCreate; i++) { String competitorName = "C" + i; - Competitor competitor = new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", boatClass, null)); result.add(competitor); } diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RaceLogSerializationTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RaceLogSerializationTest.java index 27bf9672fff..9df60e2713f 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RaceLogSerializationTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RaceLogSerializationTest.java @@ -49,8 +49,8 @@ public class RaceLogSerializationTest { private JsonDeserializer deserializer; private final AbstractLogEventAuthor author = new LogEventAuthorImpl("test", 0); private static final Competitor competitor = new CompetitorImpl("a", "b", null, - new TeamImpl("a", Collections.singletonList(new PersonImpl("a", new NationalityImpl("GER"), new Date(), "abc")), null), - new BoatImpl("a", new BoatClassImpl("a", true), "abc")); + null, + new TeamImpl("a", Collections.singletonList(new PersonImpl("a", new NationalityImpl("GER"), new Date(), "abc")), null), new BoatImpl("a", new BoatClassImpl("a", true), "abc")); @Before public void setup() { diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java index 308954f2376..6ec2d9293be 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java @@ -70,11 +70,11 @@ public abstract class AbstractJaxRsApiTest { BoatClass boatClass = new BoatClassImpl("505", /* typicallyStartsUpwind */ true); for (int i = 1; i <= numberOfCompetitorsToCreate; i++) { String competitorName = "C" + i; - Competitor competitor = new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( - new PersonImpl(competitorName, new NationalityImpl("GER"), - /* dateOfBirth */ null, "This is famous "+competitorName)), - new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + Competitor competitor = new CompetitorImpl(123, competitorName, Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", boatClass, null)); result.add(competitor); } diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index 749426ba010..d3afc5356a4 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -217,7 +217,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -227,8 +227,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -449,7 +449,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitor2UUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -457,7 +457,7 @@ public class MasterDataImportTest { DynamicPerson coach2 = new PersonImpl("Max Test", new NationalityImpl("GER"), new Date(645487200000L), "desc"); DynamicTeam team2 = new TeamImpl("Pros2", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("FastBoat", boatClass, "GER70133"); - CompetitorImpl competitor2 = new CompetitorImpl(competitor2UUID, "Froderik", Color.RED, team2, boat2); + CompetitorImpl competitor2 = new CompetitorImpl(competitor2UUID, "Froderik", Color.RED, null, team2, boat2); competitors.add(competitor2); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -604,7 +604,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); @@ -905,7 +905,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -915,8 +915,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -1104,7 +1104,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1114,8 +1114,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -1399,7 +1399,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1409,8 +1409,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -1533,7 +1533,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1543,8 +1543,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); @@ -1749,7 +1749,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1759,8 +1759,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); @@ -1978,7 +1978,7 @@ public class MasterDataImportTest { DynamicTeam team = new TeamImpl("Pros", sailors, coach); BoatClass boatClass = new BoatClassImpl("H16", true); DynamicBoat boat = new BoatImpl("Wingy", boatClass, "GER70133"); - CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, team, boat); + CompetitorImpl competitor = new CompetitorImpl(competitorUUID, "Froderik", Color.RED, null, team, boat); competitors.add(competitor); UUID competitorToSuppressUUID = UUID.randomUUID(); Set sailors2 = new HashSet(); @@ -1988,8 +1988,8 @@ public class MasterDataImportTest { "Bester Coach"); DynamicTeam team2 = new TeamImpl("Noobs", sailors2, coach2); DynamicBoat boat2 = new BoatImpl("LahmeEnte", boatClass, "GER1337"); - CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, team2, - boat2); + CompetitorImpl competitorToSuppress = new CompetitorImpl(competitorToSuppressUUID, "Merkel", Color.RED, null, + team2, boat2); competitors.add(competitorToSuppress); TrackedRace trackedRace = new DummyTrackedRace(raceId, competitors, regatta, null, sourceService.getWindStore()); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java index 17a2e2fbb73..90c34fcf353 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OfflineDataManager.java @@ -90,11 +90,11 @@ public class OfflineDataManager extends DataManager { medal), new EmptyRegattaConfiguration()); List competitors = new ArrayList(); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.BLUE, null, null)); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "The Wave Muscat", Color.LIGHT_GRAY, null, null)); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "Red Bull Extreme Sailing Team", Color.RED, null, null)); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "Team Korea", Color.GREEN, null, null)); - competitors.add(new CompetitorImpl(UUID.randomUUID(), "Realteam", Color.BLACK, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.BLUE, null, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "The Wave Muscat", Color.LIGHT_GRAY, null, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "Red Bull Extreme Sailing Team", Color.RED, null, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "Team Korea", Color.GREEN, null, null, null)); + competitors.add(new CompetitorImpl(UUID.randomUUID(), "Realteam", Color.BLACK, null, null, null)); RaceLogEventFactory factory = new RaceLogEventFactoryImpl(); RaceLog log = new RaceLogImpl(UUID.randomUUID()); From b4142edf148ea198804afb31ebbf34505928e77c Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 01:11:51 +0100 Subject: [PATCH 052/264] use competitors registered in RegattaLog to compute getAllCompetitors() in HasRegattaLike implementations --- .../domain/test/CompetitorListenerTest.java | 5 +++++ .../sailing/domain/base/impl/RegattaImpl.java | 16 ++++++---------- .../leaderboard/LeaderboardCacheManager.java | 5 +++++ .../impl/FlexibleLeaderboardImpl.java | 19 +++++++++++++++++++ .../impl/RegattaLeaderboardImpl.java | 4 ++-- 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java index bdef38602ad..ca8acdfaa70 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorListenerTest.java @@ -94,6 +94,11 @@ public class CompetitorListenerTest extends AbstractSerializationTest { CompetitorListenerTest.this.oldColor = oldColor; CompetitorListenerTest.this.newColor = newColor; } + + @Override + public void emailChanged(String oldEmail, String newEmail) { + //TODO + } }; competitor.addCompetitorChangeListener(listener); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index 75c0b58a29e..c7d278d2065 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -12,8 +12,7 @@ import java.util.logging.Logger; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; -import com.sap.sailing.domain.abstractlog.shared.events.RegisterCompetitorEvent; +import com.sap.sailing.domain.abstractlog.shared.analyzing.RegisteredCompetitorsAnalyzer; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; @@ -306,14 +305,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene result.add(c); } } - - RegattaLog regattaLog = regattaLikeHelper.getRegattaLog(); - for (RegattaLogEvent event : regattaLog.getUnrevokedEvents()){ - if (event instanceof RegisterCompetitorEvent){ - RegisterCompetitorEvent competitorRegistrationEvent = (RegisterCompetitorEvent) event; - result.add(competitorRegistrationEvent.getCompetitor()); - } - } + + //consider {@link RegattaLog} + Set viaLog = new RegisteredCompetitorsAnalyzer<>(regattaLikeHelper.getRegattaLog()).analyze(); + result.addAll(viaLog); + return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java index 481d63b3cc3..6dfe6c5521a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/LeaderboardCacheManager.java @@ -116,6 +116,11 @@ public class LeaderboardCacheManager { competitor.removeCompetitorChangeListener(this); } } + + @Override + public void emailChanged(String oldEmail, String newEmail) { + //ignore (email not shown in leaderboard) + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java index aee04ead6da..a3747fa95ab 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java @@ -6,10 +6,14 @@ import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.logging.Logger; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.abstractlog.shared.analyzing.RegisteredCompetitorsAnalyzer; +import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; @@ -275,4 +279,19 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements public void removeListener(RegattaLikeListener listener) { regattaLikeHelper.removeListener(listener); } + + @Override + public Iterable getAllCompetitors() { + Set result = new HashSet<>(); + + for (Competitor c : super.getAllCompetitors()) { + result.add(c); + } + + //consider {@link RegattaLog} + Set viaLog = new RegisteredCompetitorsAnalyzer<>(regattaLikeHelper.getRegattaLog()).analyze(); + result.addAll(viaLog); + + return result; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java index a78f6ede725..7979486e85f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java @@ -100,13 +100,13 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R @Override public Iterable getAllCompetitors() { - HashSet competitors = new HashSet(); - + HashSet competitors = new HashSet<>(); for(Competitor competitor : super.getAllCompetitors()){ competitors.add(competitor); } + // regatta considers {@link RegattaLog} as well for(Competitor competitor : regatta.getAllCompetitors()){ competitors.add(competitor); } From fe5bf1efcba419c801c454fcbe255bfa5abca68c Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 01:27:00 +0100 Subject: [PATCH 053/264] moved TrackingStringMessages to correct folder --- .../.settings/org.eclipse.core.resources.prefs | 2 +- .../{ => stringmessages}/TrackingStringMessages_de.properties | 0 .../{ => stringmessages}/TrackingStringMessages_en.properties | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename java/com.sap.sailing.domain.racelogtrackingadapter/resources/{ => stringmessages}/TrackingStringMessages_de.properties (100%) rename java/com.sap.sailing.domain.racelogtrackingadapter/resources/{ => stringmessages}/TrackingStringMessages_en.properties (100%) diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs index 43eec3e2c6e..1b6fd8593f0 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/.settings/org.eclipse.core.resources.prefs @@ -1,2 +1,2 @@ eclipse.preferences.version=1 -encoding//resources/TrackingStringMessages_de.properties=UTF-8 +encoding//resources/stringmessages/TrackingStringMessages_de.properties=UTF-8 diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_de.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties similarity index 100% rename from java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_de.properties rename to java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_en.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties similarity index 100% rename from java/com.sap.sailing.domain.racelogtrackingadapter/resources/TrackingStringMessages_en.properties rename to java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties From 0e8480d908e9cb9b3df21b86d3fda2fb3ab9a31b Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 01:29:59 +0100 Subject: [PATCH 054/264] add MailService activator in manifest --- java/com.sap.sse.mail/META-INF/MANIFEST.MF | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sse.mail/META-INF/MANIFEST.MF b/java/com.sap.sse.mail/META-INF/MANIFEST.MF index 2402b0d40ef..ccf3a8d0850 100755 --- a/java/com.sap.sse.mail/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.mail/META-INF/MANIFEST.MF @@ -5,6 +5,7 @@ Bundle-SymbolicName: com.sap.sse.mail Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-Activator: com.sap.sse.mail.impl.Activator Export-Package: com.sap.sse.mail Import-Package: javax.mail, javax.mail.internet, From aade5bbb027431ae937c9ec73d316b3eb9df4ca4 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 02:17:21 +0100 Subject: [PATCH 055/264] pass exception during mail sending along --- .../src/com/sap/sse/common/mail/MailException.java | 4 ++++ .../src/com/sap/sse/mail/impl/MailServiceImpl.java | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java b/java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java index 84c9ab3e5e4..a52525a7784 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/mail/MailException.java @@ -10,4 +10,8 @@ public class MailException extends Exception implements Serializable { public MailException(String message) { super(message); } + + public MailException(String message, Throwable caught) { + super(message, caught); + } } diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java index 7ef047c3a5c..c638aeb4618 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java @@ -70,6 +70,10 @@ public class MailServiceImpl implements ReplicableMailService, ClearStateTestSup msg.setSubject(subject); contentSetter.setContent(msg); msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); + + // for testing with gmail + //Transport ts = session.getTransport("smtps"); + Transport ts = session.getTransport(); ts.connect(); ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); @@ -77,7 +81,7 @@ public class MailServiceImpl implements ReplicableMailService, ClearStateTestSup logger.info("mail sent to " + toAddress + " with subject " + subject); } catch (MessagingException e) { logger.log(Level.SEVERE, "Error trying to send mail to " + toAddress, e); - throw new MailException(e.getMessage()); + throw new MailException(e.getMessage(), e); } } } else { From 561c6470d43d809858eb915ec84b06b194f7514b Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 02:49:16 +0100 Subject: [PATCH 056/264] make invitation mail prettier --- .../stringmessages/TrackingStringMessages_de.properties | 7 ++++--- .../stringmessages/TrackingStringMessages_en.properties | 5 +++-- .../racelogtracking/impl/RaceLogTrackingAdapterImpl.java | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties index 3f4537991ef..21a7ad3191e 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties @@ -1,3 +1,4 @@ -trackingInvitationFor=Einladung zum Tracken für -scanQRCodeOrVisitUrlToRegisterAs=Scanne den QRCode oder besuche die URL mit deinem Smartphone um dich zu registrieren als -welcomeTo=Willkommen zu \ No newline at end of file +trackingInvitationFor=Einladung zum Tracken für +scanQRCodeOrVisitUrlToRegisterAs=Scanne den QRCode oder klicke den Link mit deinem Smartphone an um dich zu registrieren als +welcomeTo=Willkommen zu +alternativelyVisitThisLink=Alternativ kannst du diesen Link anklicken \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties index a0993d18681..4edebbdcda5 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties @@ -1,3 +1,4 @@ trackingInvitationFor=Tracking Invitation for -scanQRCodeOrVisitUrlToRegisterAs=Scan the following QRCode or visit the URL using your smartphone to register as -welcomeTo=Welcome to \ No newline at end of file +scanQRCodeOrVisitUrlToRegisterAs=Scan the following QRCode or click the link using your smartphone to register as +welcomeTo=Welcome to +alternativelyVisitThisLink=Alternatively, you can click this Link \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index e4a4fff55b4..52377218094 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -375,17 +375,17 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { // taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm BodyPart messageTextPart = new MimeBodyPart(); String htmlText = String.format("

    %s %s

    " + "

    %s %s

    " - + "" + + "
    " + "%s", RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "welcomeTo"), leaderboardName, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), competitorName, - url, url); + url, url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink")); try { messageTextPart.setContent(htmlText, "text/html"); BodyPart messageImagePart = new MimeBodyPart(); - InputStream imageIs = QRCodeGenerationUtil.create(url, 128); + InputStream imageIs = QRCodeGenerationUtil.create(url, 250); DataSource imageDs = new ByteArrayDataSource(imageIs, "image/png"); messageImagePart.setDataHandler(new DataHandler(imageDs)); messageImagePart.setHeader("Content-ID", ""); From 4490d6a35d5314e0c5dd5682eecc1a2431ec76ed Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 02:50:13 +0100 Subject: [PATCH 057/264] fix buggy behaviour with multipart image as email attachment --- .../src/com/sap/sse/mail/impl/MailServiceImpl.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java index c638aeb4618..78042958f07 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java @@ -65,15 +65,21 @@ public class MailServiceImpl implements ReplicableMailService, ClearStateTestSup if (toAddress != null) { Session session = Session.getInstance(mailProperties, new SMTPAuthenticator()); MimeMessage msg = new MimeMessage(session); + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); try { msg.setFrom(new InternetAddress(mailProperties.getProperty("mail.from", "root@sapsailing.com"))); msg.setSubject(subject); - contentSetter.setContent(msg); msg.addRecipient(RecipientType.TO, new InternetAddress(toAddress.trim())); + // this fixes the DCH MIME type error + // see http://tanyamadurapperuma.blogspot.de/2014/01/struggling-with-nosuchproviderexception.html + Thread.currentThread().setContextClassLoader(javax.mail.Session.class.getClassLoader()); + + contentSetter.setContent(msg); + // for testing with gmail //Transport ts = session.getTransport("smtps"); - + Transport ts = session.getTransport(); ts.connect(); ts.sendMessage(msg, msg.getRecipients(RecipientType.TO)); @@ -82,6 +88,8 @@ public class MailServiceImpl implements ReplicableMailService, ClearStateTestSup } catch (MessagingException e) { logger.log(Level.SEVERE, "Error trying to send mail to " + toAddress, e); throw new MailException(e.getMessage(), e); + } finally { + Thread.currentThread().setContextClassLoader(oldClassLoader); } } } else { From 4da7cfa6eb35c3599b8ce5408a440daa85e94bf2 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 03:06:32 +0100 Subject: [PATCH 058/264] reflection in URLEncode to avoid tycho compilation error --- .../src/com/sap/sse/common/URLEncoder.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/URLEncoder.java b/java/com.sap.sse.common/src/com/sap/sse/common/URLEncoder.java index 19384a77d13..20ecefd9ecd 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/URLEncoder.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/URLEncoder.java @@ -1,5 +1,6 @@ package com.sap.sse.common; +import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -11,9 +12,9 @@ public class URLEncoder { */ public static String encode(String decodedUrlString) { try { - Class.forName("com.google.gwt.http.client.URL"); + Class clazz = Class.forName("com.google.gwt.http.client.URL"); // GWT context - return com.google.gwt.http.client.URL.encode(decodedUrlString); + return (String) clazz.getMethod("encode", String.class).invoke(/* object */null, decodedUrlString); } catch (ClassNotFoundException e) { // non-GWT context (URL/URI should be fully available) try { @@ -22,9 +23,11 @@ public class URLEncoder { url.getQuery(), url.getRef()); return uri.toString(); } catch (MalformedURLException | URISyntaxException f) { - f.printStackTrace(); return null; } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException + | SecurityException e) { + return null; } } } From 52fa52efceb26e56829d0db892c4d3013cf83f86 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Tue, 10 Feb 2015 03:36:05 +0100 Subject: [PATCH 059/264] choose complicated approach for encoding URLs, as tycho doesn't allow com.google.gwt references in common bundles --- .../tracking/DeviceMappingConstants.java | 10 +++--- .../impl/RaceLogTrackingAdapterImpl.java | 3 +- .../AddDeviceMappingToRaceLogDialog.java | 4 ++- .../AddDeviceMappingToRegattaLogDialog.java | 3 +- .../sailing/gwt/ui/client/GwtUrlHelper.java | 22 +++++++++++++ java/com.sap.sse.common/META-INF/MANIFEST.MF | 5 +-- .../src/com/sap/sse/common/URLEncoder.java | 33 ------------------- .../com/sap/sse/common/util/UrlHelper.java | 9 +++++ .../sap/sse/util/impl/NonGwtUrlHelper.java | 23 +++++++++++++ 9 files changed, 69 insertions(+), 43 deletions(-) create mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GwtUrlHelper.java delete mode 100644 java/com.sap.sse.common/src/com/sap/sse/common/URLEncoder.java create mode 100644 java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java create mode 100644 java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java index 88a537cf67f..5e468744ffb 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/DeviceMappingConstants.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.common.racelog.tracking; import com.sap.sailing.domain.common.racelog.RaceLogServletConstants; -import com.sap.sse.common.URLEncoder; +import com.sap.sse.common.util.UrlHelper; /** * Shared between GWT and Android. Used for creating and deciphering the URL encoded by the QRCode, which gives the @@ -42,8 +42,8 @@ public class DeviceMappingConstants { static final String APK_PATH = "/apps/com.sap.sailing.android.tracking.app.apk"; public static String getDeviceMappingForRegattaLogUrl(String serverUrlWithoutTrailingSlash, String eventId, - String leaderboardName, String mappedItemType, String mappedItemId) { - return URLEncoder.encode(serverUrlWithoutTrailingSlash + URL_BASE + "?" + URL_EVENT_ID + "=" + eventId + "&" + String leaderboardName, String mappedItemType, String mappedItemId, UrlHelper helper) { + return helper.encodeUrl(serverUrlWithoutTrailingSlash + URL_BASE + "?" + URL_EVENT_ID + "=" + eventId + "&" + URL_LEADERBOARD_NAME + "=" + leaderboardName + "&" + mappedItemType + "=" + mappedItemId); } @@ -51,11 +51,11 @@ public class DeviceMappingConstants { @Deprecated public static String getDeviceMappingForRaceLogUrl(String serverUrlWithoutTrailingSlash, String leaderboardName, String raceColumnName, String fleetName, String mappedItemType, String mappedItemId, long fromMillis, - long toMillis) throws QRCodeURLCreationException { + long toMillis, UrlHelper helper) throws QRCodeURLCreationException { if (fromMillis > toMillis) { throw new QRCodeURLCreationException("from can't lie after to"); } - return URLEncoder.encode(serverUrlWithoutTrailingSlash + DeviceMappingConstants.APK_PATH + "?" + return helper.encodeUrl(serverUrlWithoutTrailingSlash + DeviceMappingConstants.APK_PATH + "?" + RaceLogServletConstants.PARAMS_LEADERBOARD_NAME + "=" + leaderboardName + "&" + RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME + "=" + raceColumnName + "&" + RaceLogServletConstants.PARAMS_RACE_FLEET_NAME + "=" + fleetName + "&" + mappedItemType + "=" diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index 52377218094..b828c230945 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -80,6 +80,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.mail.MailException; import com.sap.sse.common.qrcode.QRCodeGenerationUtil; import com.sap.sse.mail.MailService; +import com.sap.sse.util.impl.NonGwtUrlHelper; public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { private static final Logger logger = Logger.getLogger(RaceLogTrackingAdapterImpl.class.getName()); @@ -368,7 +369,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash, event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING, - competitor.getId().toString()); + competitor.getId().toString(), NonGwtUrlHelper.INSTANCE); String subject = String.format("%s %s", RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "trackingInvitationFor"), competitorName); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java index f005264b10d..74aa4170a45 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRaceLogDialog.java @@ -22,6 +22,7 @@ import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.common.racelog.tracking.MappableToDevice; import com.sap.sailing.domain.common.racelog.tracking.QRCodeURLCreationException; import com.sap.sailing.gwt.ui.adminconsole.ItemToMapToDeviceSelectionPanel.SelectionChangedHandler; +import com.sap.sailing.gwt.ui.client.GwtUrlHelper; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; @@ -149,7 +150,8 @@ public class AddDeviceMappingToRaceLogDialog extends DataEntryDialog clazz = Class.forName("com.google.gwt.http.client.URL"); - // GWT context - return (String) clazz.getMethod("encode", String.class).invoke(/* object */null, decodedUrlString); - } catch (ClassNotFoundException e) { - // non-GWT context (URL/URI should be fully available) - try { - URL url = new URL(decodedUrlString); - URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), - url.getQuery(), url.getRef()); - return uri.toString(); - } catch (MalformedURLException | URISyntaxException f) { - return null; - } - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException - | SecurityException e) { - return null; - } - } -} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java b/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java new file mode 100644 index 00000000000..0b55cf0da1c --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java @@ -0,0 +1,9 @@ +package com.sap.sse.common.util; + +/** + * Context-independent interface for dealing with URLs. + * GWT context does not offer full implementations of URL/URI. * + */ +public interface UrlHelper { + String encodeUrl(String decodedUrlString); +} diff --git a/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java b/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java new file mode 100644 index 00000000000..5a19f9d7fd8 --- /dev/null +++ b/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java @@ -0,0 +1,23 @@ +package com.sap.sse.util.impl; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import com.sap.sse.common.util.UrlHelper; + +public enum NonGwtUrlHelper implements UrlHelper { + INSTANCE; + + public String encodeUrl(String decodedUrlString) { + try { + URL url = new URL(decodedUrlString); + URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), + url.getQuery(), url.getRef()); + return uri.toString(); + } catch (MalformedURLException | URISyntaxException f) { + return null; + } + } +} From c477d0ed1e78659d9ec96a2f076329458f10d837 Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 10 Feb 2015 10:30:49 +0100 Subject: [PATCH 060/264] fixed gw module name in pom.xml of theme bundle --- java/com.sap.sse.gwt.theme/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sse.gwt.theme/pom.xml b/java/com.sap.sse.gwt.theme/pom.xml index 3770ea11724..eb3460c70c9 100644 --- a/java/com.sap.sse.gwt.theme/pom.xml +++ b/java/com.sap.sse.gwt.theme/pom.xml @@ -62,7 +62,7 @@ -Dgwt.persistentunitcache=false - com.sap.sse.gwt.theme.Theme + com.sap.sse.gwt.theme.SSETheme ${basedir}/.generated 7 From ead0989ffd2e865dbf33fa86cfc068aa12ca611d Mon Sep 17 00:00:00 2001 From: Papick Garcia Taboada Date: Tue, 10 Feb 2015 19:58:39 +0100 Subject: [PATCH 061/264] Reduced margins for leaderboard elements --- .../place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml | 2 +- .../client/place/event/oldleaderboard/OldLeaderboard.ui.xml | 2 +- .../place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml | 2 +- .../place/event/regattaleaderboard/EventRegattaLeaderboard.css | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml index 997e1bc24b5..8ec9eaf05ca 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml @@ -7,7 +7,7 @@ -
    +

    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml index 8f65326ebe2..56cdf5996d4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml @@ -7,7 +7,7 @@ -
    +

    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml index 5b78dfeda4e..b5061cfb674 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml @@ -7,7 +7,7 @@ -
    +

    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css index dec9fb3e38a..427b670805c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaleaderboard/EventRegattaLeaderboard.css @@ -38,6 +38,8 @@ text-align: center; } .regattaleaderboard { + margin-left: 5rem; + margin-right: 5rem; font-size: 15px; font-size: 1rem; width: 100%; From 17370f1ca0f7701e561d4689222394b2bebfdf5a Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 11 Feb 2015 10:06:52 +0100 Subject: [PATCH 062/264] Added filter option for rank of fix's competitor --- .../polars/mining/PolarFixFilterCriteria.java | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java index a6d5315122d..d1f872f9023 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java @@ -2,6 +2,7 @@ package com.sap.sailing.polars.mining; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; @@ -10,13 +11,49 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.datamining.components.FilterCriterion; public class PolarFixFilterCriteria implements FilterCriterion { + + /** + * 0 if every competitor should be included. + * 1 if only the leading competitor should be included and so on. + */ + private final int numberOfLeadingCompetitorsToInclude; + + /** + * + * @param numberOfLeadingCompetitorsToInclude + * 0 if every competitor should be included.
    + * 1 if only the leading competitor should be included and so on. + */ + public PolarFixFilterCriteria(int numberOfLeadingCompetitorsToInclude) { + this.numberOfLeadingCompetitorsToInclude = numberOfLeadingCompetitorsToInclude; + } + + public PolarFixFilterCriteria() { + this.numberOfLeadingCompetitorsToInclude = 0; + } @Override public boolean matches(GPSFixMovingWithPolarContext element) { boolean afterStartTime = isAfterStartTime(element); boolean beforeFinishTime = isBeforeFinishTime(element); boolean noDirectionChange = !hasDirectionChange(element); - return (afterStartTime && beforeFinishTime && noDirectionChange); + boolean isInLeadingCompetitors = true; + if (numberOfLeadingCompetitorsToInclude > 0) { + isInLeadingCompetitors = isInLeadingCompetitors(element); + } + return (afterStartTime && beforeFinishTime && noDirectionChange && isInLeadingCompetitors); + } + + private boolean isInLeadingCompetitors(GPSFixMovingWithPolarContext element) { + boolean result; + try { + int rank = element.getRace().getRank(element.getCompetitor(), element.getFix().getTimePoint()); + result = rank <= numberOfLeadingCompetitorsToInclude; + } catch (NoWindException e) { + // Unrealistic, we wouldn't be here if there was no wind. Let's return false. + result = false; + } + return result; } private boolean hasDirectionChange(GPSFixMovingWithPolarContext element) { From b2cbc90102b06b0cf48cdee83d591c2c7ba4022f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 10:20:50 +0100 Subject: [PATCH 063/264] made SailingServiceImpl.getReplicationService() private again --- .../java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 2 +- java/com.sap.sse.security.ui/Security UI sdm.launch | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 50ef1ff41e6..f2fa3e0479c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -2168,7 +2168,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return racingEventServiceTracker.getService(); // grab the service } - protected ReplicationService getReplicationService() { + private ReplicationService getReplicationService() { return replicationServiceTracker.getService(); } diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch index 56e5f53b58d..4655b59b08e 100755 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -45,5 +45,5 @@ - + From ea7d158b0bca233bf8be613b3c405c8124874469 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 10:22:43 +0100 Subject: [PATCH 064/264] made SailingServiceImpl.getDeviceIdentifierStringSerializerHandlerFinder(...) private again --- .../java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index f2fa3e0479c..f222d29411c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -4995,7 +4995,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S getRaceLogTrackingAdapter().copyCourseAndCompetitors(fromRaceLog, toRaceLogs, baseDomainFactory, getService()); } - protected TypeBasedServiceFinder getDeviceIdentifierStringSerializerHandlerFinder( + private TypeBasedServiceFinder getDeviceIdentifierStringSerializerHandlerFinder( boolean withFallback) { TypeBasedServiceFinderFactory factory = getService().getTypeBasedServiceFinderFactory(); TypeBasedServiceFinder finder = factory.createServiceFinder(DeviceIdentifierStringSerializationHandler.class); From 518b86651e5e13d3b9b81f7017334169b5689641 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 10:23:51 +0100 Subject: [PATCH 065/264] made SailingServiceImpl.getFileStorageService() private again --- .../java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index f222d29411c..9e309cfba06 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -5358,7 +5358,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return data; } - protected FileStorageService getFileStorageService(String name) { + private FileStorageService getFileStorageService(String name) { if (name == null || name.equals("")) { return null; } From eb3cf53352cdfdd746c865623e57874789f409fc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 10:28:03 +0100 Subject: [PATCH 066/264] made getReplicationService() protected again on SailingServiceImpl because it's overridden in SailingServiceImplMock --- .../java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 9e309cfba06..b4ea9ba73da 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -2168,7 +2168,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return racingEventServiceTracker.getService(); // grab the service } - private ReplicationService getReplicationService() { + protected ReplicationService getReplicationService() { return replicationServiceTracker.getService(); } From b610aa11c62fa14be8ffb94f7a99ec7efd2b5e5b Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 11 Feb 2015 13:03:54 +0100 Subject: [PATCH 067/264] a try to set the chart height in a different way --- .../shared/charts/AbstractCompetitorLeaderboardChart.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java index feb71eaee46..515f6233390 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java @@ -27,6 +27,7 @@ import org.moxieapps.gwt.highcharts.client.plotOptions.Marker; import org.moxieapps.gwt.highcharts.client.plotOptions.Marker.Symbol; import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.Widget; @@ -296,8 +297,8 @@ public abstract class AbstractCompetitorLeaderboardChart extends A raceColumnNumber++; } } - chart.getYAxis().setMaxPadding(0.5 / maxCompetitorCount).setMinPadding(0.5 / maxCompetitorCount); - chart.setHeight(maxCompetitorCount * 30 + 100 + "px"); +// chart.getYAxis().setMaxPadding(0.5 / maxCompetitorCount).setMinPadding(0.5 / maxCompetitorCount); + chart.setHeight(Window.getClientHeight()); // maxCompetitorCount * 30 + 100 + "px"); } private void fillTotalPointsSeries( From a5507a16b313424ca6d48072b967ad18956ceb44 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Wed, 11 Feb 2015 13:16:47 +0100 Subject: [PATCH 068/264] remove unnecessary mockito dependency that let build fail --- java/com.sap.sse.security.test/META-INF/MANIFEST.MF | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sse.security.test/META-INF/MANIFEST.MF b/java/com.sap.sse.security.test/META-INF/MANIFEST.MF index 101740dfa00..1247c3ec236 100644 --- a/java/com.sap.sse.security.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.test/META-INF/MANIFEST.MF @@ -10,5 +10,4 @@ Require-Bundle: org.junit4;bundle-version="4.8.2", com.sap.sse.security.userstore.mongodb, com.mongodb.driver, com.sap.sse.mongodb, - org.mockito.mockito-core, com.sap.sse.mail From 70b5d67bc50379d9aeac2ad619fcddf5e4065259 Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 11 Feb 2015 13:18:26 +0100 Subject: [PATCH 069/264] fixed image link in whats new section of sailing analytics --- .../sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html index f60f48d60d4..5d21493a540 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html @@ -168,7 +168,7 @@
  • China races, such as in Qingdao, may have the map background displayed at slightly wrong locations. This is due to an intentional random transformation that Google applies to GPS locations for China. You can circumvent this issue by switching to satellite view where the map background is displayed - correctly. + correctly.
  • Fixed a bug that in some cases has caused the wind chart, the advantage line, the wind indicator on the map and the leaderboard to use slightly different wind values. The problem was related to different averaging and From 3875409363923a8fdcfc2f543088cc718c078080 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Wed, 11 Feb 2015 13:44:42 +0100 Subject: [PATCH 070/264] remove unused class that was added for email invites --- .../EventListCompositeDialog.java | 73 ------------------- 1 file changed, 73 deletions(-) delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListCompositeDialog.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListCompositeDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListCompositeDialog.java deleted file mode 100644 index dd33f41f378..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListCompositeDialog.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import java.util.List; -import java.util.UUID; - -import com.google.gwt.user.client.ui.VerticalPanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.ui.client.DataEntryDialogWithBootstrap; -import com.sap.sailing.gwt.ui.client.EventSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.EventSelectionModel; -import com.sap.sailing.gwt.ui.client.EventSelectionProvider; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sse.gwt.client.ErrorReporter; - -public class EventListCompositeDialog extends DataEntryDialogWithBootstrap> implements EventSelectionChangeListener { - protected StringMessages stringMessages; - - private EventSelectionProvider eventSelectionProvider; - private EventListComposite eventListComposite; - private List selectedEvents; - - protected static class EventListValidator implements Validator> { - private StringMessages stringMessages; - - public EventListValidator(StringMessages stringMessages) { - this.stringMessages = stringMessages; - } - - @Override - public String getErrorMessage(List valueToValidate) { - if (valueToValidate.isEmpty()){ - return stringMessages.pleaseSelectAnEvent(); - } - return null; - } - } - - public EventListCompositeDialog(SailingServiceAsync sailingService, StringMessages stringMessages,final ErrorReporter errorReporter, DialogCallback> callback) { - super(stringMessages.event(), null, stringMessages.ok(), stringMessages.cancel(), new EventListValidator(stringMessages), callback); - this.stringMessages = stringMessages; - - eventSelectionProvider = new EventSelectionModel(true); - eventSelectionProvider.addEventSelectionChangeListener(this); - - eventListComposite = new EventListComposite(sailingService, eventSelectionProvider, errorReporter, stringMessages); - eventListComposite.ensureDebugId("EventListComposite"); - } - - @Override - protected List getResult() { - return selectedEvents; - } - - @Override - protected Widget getAdditionalWidget() { - final VerticalPanel panel = new VerticalPanel(); - panel.setWidth("100%"); - Widget additionalWidget = super.getAdditionalWidget(); - if (additionalWidget != null) { - panel.add(additionalWidget); - } - - panel.add(eventListComposite); - - return panel; - } - - @Override - public void onEventSelectionChange(List selectedEvents) { - this.selectedEvents = selectedEvents; - } -} From 143ae6aeee8bdcf454e26603dce0997e57f4ea87 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 14:12:45 +0100 Subject: [PATCH 071/264] use the same setHeight() implementation for both types of competitor leaderboard charts (points / ranks) --- .../SailingGWT sdm.launch | 21 +++++++++---------- .../AbstractCompetitorLeaderboardChart.java | 10 +++++---- .../Security UI sdm.launch | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index 74c0ed79c05..95f290cbd5c 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -4,19 +4,19 @@ - - - - - - - - - + + + + + + + + + @@ -30,7 +30,6 @@ - @@ -69,7 +68,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java index 515f6233390..ad6cd88a09c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java @@ -297,8 +297,11 @@ public abstract class AbstractCompetitorLeaderboardChart extends A raceColumnNumber++; } } -// chart.getYAxis().setMaxPadding(0.5 / maxCompetitorCount).setMinPadding(0.5 / maxCompetitorCount); - chart.setHeight(Window.getClientHeight()); // maxCompetitorCount * 30 + 100 + "px"); + setHeight(); + } + + private void setHeight() { + chart.setHeight(Window.getClientHeight()); } private void fillTotalPointsSeries( @@ -342,8 +345,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A raceColumnNumber++; } } - chart.getYAxis().setMaxPadding(0.5/maxCompetitorCount).setMinPadding(0.5/maxCompetitorCount); - chart.setHeight(maxCompetitorCount * 30 + 100 + "px"); + setHeight(); } private boolean hasValidValues(List values) { diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch index 56e5f53b58d..4655b59b08e 100755 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -45,5 +45,5 @@ - + From b9c6fea20e37d2745a02c6072777403f5e9f883e Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Wed, 11 Feb 2015 14:31:54 +0100 Subject: [PATCH 072/264] adapt selenium test to match new behaviour of RegattaLeaderboard#getAllCompetitors() - test expected leaderboard to contain no competitors before linking tracked races - however, the Regatta underlying the leaderboard already new the competitors - with the new implementation, a RegattaLeaderboard also uses the competitors known to the regatta --- .../selenium/test/leaderboard/TestLeaderboardConfiguration.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java index d337eba26b9..a718f6ac05f 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java @@ -84,7 +84,6 @@ public class TestLeaderboardConfiguration extends AbstractSeleniumTest { List races = table.getRaceNames(); assertThat(races.size(), equalTo(5)); - assertThat(table.getEntries().size(), equalTo(0)); adminConsoleWindow.switchToWindow(); AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); From 79e27fad8a59f0a30c5e2b27241e94cba3884b73 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 14:39:39 +0100 Subject: [PATCH 073/264] proposal for bug2515: chart width remains unchanged; height always consumes available vertical window space --- .../shared/charts/AbstractCompetitorLeaderboardChart.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java index ad6cd88a09c..b64a0a0b658 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java @@ -246,7 +246,9 @@ public abstract class AbstractCompetitorLeaderboardChart extends A break; } - chart.setSizeToMatchContainer(); + // TODO will removing the following line do harm on any usage of this abstract base class? + // chart.setSizeToMatchContainer(); + // it's important here to recall the redraw method, otherwise the bug fix for wrong checkbox // positions (nativeAdjustCheckboxPosition) // in the BaseChart class would not be called @@ -301,7 +303,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A } private void setHeight() { - chart.setHeight(Window.getClientHeight()); + chart.setSize(chart.getOffsetWidth(), Window.getClientHeight()); } private void fillTotalPointsSeries( From 40f0ac5bbd55b2110d132baff48361ec76c8aab4 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 15:47:22 +0100 Subject: [PATCH 074/264] RegattaLeaderboard.getAllCompetitors() now directly delegates to Regatta.getAllCompetitors() because it does the same thing --- .../sailing/domain/base/impl/RegattaImpl.java | 2 -- .../domain/leaderboard/Leaderboard.java | 4 +++- .../impl/AbstractLeaderboardImpl.java | 4 ++++ .../impl/FlexibleLeaderboardImpl.java | 3 --- .../impl/RegattaLeaderboardImpl.java | 24 ++++++++----------- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index c7d278d2065..d90cff8adca 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -305,11 +305,9 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene result.add(c); } } - //consider {@link RegattaLog} Set viaLog = new RegisteredCompetitorsAnalyzer<>(regattaLikeHelper.getRegattaLog()).analyze(); result.addAll(viaLog); - return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java index d2f0bcc4713..2f7db478424 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java @@ -88,7 +88,9 @@ public interface Leaderboard extends LeaderboardBase { * in a regular view of the leaderboard. Editors for leaderboards shall, though, also display the suppressed competitors * together with a visual indication showing which competitors are currently suppressed. The "suppressed-state" of a * competitor can change over the life cycle of a leaderboard. A typical use case for suppressing a competitor is - * removing one-time entries from a series (meta-)leaderboard or suppressing a camera boat in scoring. + * removing one-time entries from a series (meta-)leaderboard or suppressing a camera boat in scoring.

    + * + * @return all competitors in this leaderboard, including the suppressed ones. */ Iterable getAllCompetitors(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java index f68c204399f..1a447be2b5f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java @@ -70,6 +70,10 @@ public abstract class AbstractLeaderboardImpl extends AbstractSimpleLeaderboardI return Collections.unmodifiableSet(trackedRaces); } + /** + * This default implementation collects all competitors by visiting all {@link TrackedRace}s associated with this + * leaderboard's columns (see {@link #getTrackedRaces()}). + */ @Override public Iterable getAllCompetitors() { if (allCompetitorsCache == null) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java index a3747fa95ab..989e468e8b9 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java @@ -283,15 +283,12 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements @Override public Iterable getAllCompetitors() { Set result = new HashSet<>(); - for (Competitor c : super.getAllCompetitors()) { result.add(c); } - //consider {@link RegattaLog} Set viaLog = new RegisteredCompetitorsAnalyzer<>(regattaLikeHelper.getRegattaLog()).analyze(); result.addAll(viaLog); - return result; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java index 7979486e85f..a6df854049f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java @@ -1,15 +1,16 @@ package com.sap.sailing.domain.leaderboard.impl; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnInSeries; import com.sap.sailing.domain.base.RaceColumnListener; +import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl; @@ -98,22 +99,17 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R } } + /** + * Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this + * leaderboard's regatta which includes those belonging to {@link RaceDefinition races} + * {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's + * {@link RegattaLog}. + */ @Override public Iterable getAllCompetitors() { - HashSet competitors = new HashSet<>(); - - for(Competitor competitor : super.getAllCompetitors()){ - competitors.add(competitor); - } - - // regatta considers {@link RegattaLog} as well - for(Competitor competitor : regatta.getAllCompetitors()){ - competitors.add(competitor); - } - - return competitors; + return regatta.getAllCompetitors(); } - + @Override public IsRegattaLike getRegattaLike() { return regatta; From fe75e7899daf1e40cba49383319139f9a7343357 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 15:54:26 +0100 Subject: [PATCH 075/264] simplified CompetitorDTOImpl.hasEmail() --- .../sap/sailing/domain/common/dto/CompetitorDTOImpl.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java index 88691f0197c..6c0cee85da9 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java @@ -143,10 +143,6 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial @Override public boolean hasEmail() { - if (email == null){ - return false; - } - - return !email.isEmpty(); + return email != null && !email.isEmpty(); } } From 325d3084824537c81a63dcc0820f65a6200e2006 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 16:26:07 +0100 Subject: [PATCH 076/264] simplified implementation of CompetitorImpl.hasEmail() --- .../src/com/sap/sailing/domain/base/impl/CompetitorImpl.java | 5 +---- .../com/sap/sailing/domain/base/impl/DynamicCompetitor.java | 4 +++- .../test/leaderboard/TestLeaderboardConfiguration.java | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java index b9ffafa4c58..10d8f1bd8e1 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java @@ -132,9 +132,6 @@ public class CompetitorImpl implements DynamicCompetitor { } public boolean hasEmail(){ - if (email == null){ - return false; - } - return !email.isEmpty(); + return email != null && !email.isEmpty(); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java index 409d0475290..4b6a86578f0 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java @@ -6,8 +6,10 @@ import com.sap.sse.common.Color; public interface DynamicCompetitor extends Competitor, Renamable { DynamicBoat getBoat(); + DynamicTeam getTeam(); - + void setColor(Color displayColor); + void setEmail(String email); } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java index a718f6ac05f..a39a6dab073 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/leaderboard/TestLeaderboardConfiguration.java @@ -84,7 +84,7 @@ public class TestLeaderboardConfiguration extends AbstractSeleniumTest { List races = table.getRaceNames(); assertThat(races.size(), equalTo(5)); - + assertThat(table.getEntries().size(), equalTo(28)); // the regatta already has the races linked; regatta leaderboard obtains competitors from regatta adminConsoleWindow.switchToWindow(); AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); From 1cdb2fcfd66a2d069060727696059c94fcf9c4be Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 11 Feb 2015 17:16:09 +0100 Subject: [PATCH 077/264] Added filter option for polar mining filters out competitors that ranked worse than x after the race --- .../sailing/polars/mining/PolarDataMiner.java | 2 +- .../polars/mining/PolarFixFilterCriteria.java | 23 ++++++++++++------- .../polars/regression/BoatSpeedEstimator.java | 14 +++++++++-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index 65ca9ab3e6e..95312f7740e 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -81,7 +81,7 @@ public class PolarDataMiner implements PolarSheetAnalyzer { Collection> filteringResultReceivers = Arrays.asList(groupingProcessor); Processor filteringProcessor = new ParallelFilteringProcessor( - GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria()); + GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria(1)); Collection> enrichingResultReceivers = Arrays .asList(filteringProcessor); diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java index d1f872f9023..656b7ff5ca7 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java @@ -1,8 +1,9 @@ package com.sap.sailing.polars.mining; +import java.util.Iterator; + import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Course; -import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; @@ -45,13 +46,19 @@ public class PolarFixFilterCriteria implements FilterCriterion finishPassings = element.getRace() + .getMarkPassingsInOrder(element.getRace().getRace().getCourse().getLastWaypoint()).iterator(); + for (int i = 0; i < numberOfLeadingCompetitorsToInclude; i++) { + if (finishPassings.hasNext()) { + if (finishPassings.next().getCompetitor().equals(element.getCompetitor())) { + result = true; + break; + } + } else { + result = false; + break; + } } return result; } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java index 8c7e9203674..dcfb956e2e8 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java @@ -62,11 +62,21 @@ public class BoatSpeedEstimator implements Serializable { } public int getDataCount() { - return dataCount; + LockUtil.lockForRead(lock); + try { + return dataCount; + } finally { + LockUtil.unlockAfterRead(lock); + } } public double getConfidence() { - return confidenceSum / dataCount; + LockUtil.lockForRead(lock); + try { + return confidenceSum / dataCount; + } finally { + LockUtil.unlockAfterRead(lock); + } } private void readObject(ObjectInputStream ois) { From 26d0aa0fa4f648f72a65bfbabee514b80a1b8b0b Mon Sep 17 00:00:00 2001 From: fmittag Date: Wed, 11 Feb 2015 17:33:59 +0100 Subject: [PATCH 078/264] added a hint message for the competitor charts that the leaderboard can also be used for selection --- .../com/sap/sailing/gwt/common/client/i18n/TextMessages.java | 1 + .../sap/sailing/gwt/common/client/i18n/TextMessages.properties | 1 + .../sailing/gwt/common/client/i18n/TextMessages_de.properties | 1 + .../place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml | 3 +++ 4 files changed, 6 insertions(+) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.java index c201c80ce34..e11db038d23 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.java @@ -6,6 +6,7 @@ import com.google.gwt.i18n.client.Messages; public interface TextMessages extends Messages { public static final TextMessages INSTANCE = GWT.create(TextMessages.class); + String chartSelectionHint(); String chooseALanguage(); String home(); String events(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties index 6a50d7fa2ce..8964ef11b9d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties @@ -1,3 +1,4 @@ +chartSelectionHint=Hint: To change the displayed competitors in the chart you can also use the leaderboard. chooseALanguage=Choose a language events=Events home=Home diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties index 3afbaca10ad..97268494c95 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties @@ -1,3 +1,4 @@ +chartSelectionHint=Hinweis: Die im Diagramm angezeigten Teilnehmer können auch in der Ergebnisstabelle ausgewählt werden. chooseALanguage=Wähle eine Sprache events=Veranstaltungen home=Startseite diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml index 997e1bc24b5..5e29ee56842 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml @@ -15,6 +15,9 @@

  • +
    + +
    From d96978ef9229de1e9e4dfacf350b030b8cc0490c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 17:34:51 +0100 Subject: [PATCH 079/264] fixed RegattaImpl.getAllCompetitors() such that it not only considers RaceDefinitions owned by the regatta but also all TrackedRaces linked to the regatta's columns --- .../com/sap/sailing/domain/base/impl/RegattaImpl.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index d90cff8adca..273871543da 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -305,6 +305,16 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene result.add(c); } } + for (Series series : getSeries()) { + for (RaceColumn rc : series.getRaceColumns()) { + for (Fleet fleet : rc.getFleets()) { + TrackedRace trackedRace = rc.getTrackedRace(fleet); + if (trackedRace != null) { + Util.addAll(trackedRace.getRace().getCompetitors(), result); + } + } + } + } //consider {@link RegattaLog} Set viaLog = new RegisteredCompetitorsAnalyzer<>(regattaLikeHelper.getRegattaLog()).analyze(); result.addAll(viaLog); From a211e5427ab19e4eaef4ec9b07dd1ea60622000b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 11 Feb 2015 17:49:45 +0100 Subject: [PATCH 080/264] proposal for no e-mail validation but a placeholder attribute on the text box explaining what/why to enter --- .../sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java | 4 +--- java/com.sap.sailing.server/SailingServer (Proxy).launch | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index e7915984ae3..91c37b7bb20 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -57,9 +57,6 @@ public class CompetitorEditDialog extends DataEntryDialog { if (displayColor instanceof InvalidColor) { result = displayColor.getAsHtml(); } - } else if (valueToValidate.getEmail() == null) { - //TODO: Proper Email-Validation - result = stringMessages.pleaseEnterAnEmail(); } else if (valueToValidate.getBoatClass().getName() == null || valueToValidate.getBoatClass().getName().isEmpty()) { result = stringMessages.pleaseEnterABoatClass(); } @@ -94,6 +91,7 @@ public class CompetitorEditDialog extends DataEntryDialog { this.name = createTextBox(competitorToEdit.getName()); this.email = createTextBox(competitorToEdit.getEmail()); + this.email.getElement().setPropertyString("placeholder", stringMessages.pleaseEnterAnEmail()); this.displayColorTextBox = createTextBox(competitorToEdit.getColor() == null ? "" : competitorToEdit.getColor().getAsHtml()); this.threeLetterIocCountryCode = createListBox(/* isMultipleSelect */ false); CountryCodeFactory ccf = CountryCodeFactory.INSTANCE; diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 1eb75a15a64..bc6d8443af3 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -34,6 +34,6 @@ - + From 0c8879b643aa584cd499e9f215da86538ff462f6 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Thu, 12 Feb 2015 12:35:44 +0100 Subject: [PATCH 081/264] use lazy autostart for mail bundle in selenium test config and raceanalysis.product --- java/com.sap.sailing.feature.p2build/raceanalysis.product | 1 - java/com.sap.sailing.selenium.test/pom.xml | 5 ----- 2 files changed, 6 deletions(-) diff --git a/java/com.sap.sailing.feature.p2build/raceanalysis.product b/java/com.sap.sailing.feature.p2build/raceanalysis.product index 6f0f4447caf..71b6e30fe1b 100755 --- a/java/com.sap.sailing.feature.p2build/raceanalysis.product +++ b/java/com.sap.sailing.feature.p2build/raceanalysis.product @@ -58,7 +58,6 @@ - diff --git a/java/com.sap.sailing.selenium.test/pom.xml b/java/com.sap.sailing.selenium.test/pom.xml index 64e11bbd8b4..b6a347abc8d 100644 --- a/java/com.sap.sailing.selenium.test/pom.xml +++ b/java/com.sap.sailing.selenium.test/pom.xml @@ -88,11 +88,6 @@ 3 true - - com.sap.sse.mail - 4 - true - com.sap.sse.security 3 From 746a92d886301fb83297ce159964f33d58a1d433 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 12 Feb 2015 13:09:51 +0100 Subject: [PATCH 082/264] added again com.sap.sse.gwt.theme to main pom.xml after master merge --- java/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/java/pom.xml b/java/pom.xml index db2fd02e399..62f570dfaf8 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -28,6 +28,7 @@ com.sap.sse.operationaltransformation com.sap.sse.replication com.sap.sse.filestorage + com.sap.sse.gwt.theme com.sap.sailing.domain.common com.sap.sailing.domain.shared.android com.sap.sailing.domain From ac5941bc18b80493b94f466f93c97ec223912164 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 12 Feb 2015 13:57:40 +0100 Subject: [PATCH 083/264] added default competitor selection for the competitor chart in case of too many competitors (regatta case) --- .../RegattaAnalyticsDataManager.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java index 5e7ec9ea299..a1bf113a82f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java @@ -1,7 +1,13 @@ package com.sap.sailing.gwt.home.client.place.event.regattaanalytics; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.DebugIdHelper; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -12,6 +18,7 @@ import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.Timer; @@ -32,7 +39,8 @@ public class RegattaAnalyticsDataManager { private final UserAgentDetails userAgent; private final SailingServiceAsync sailingService; private final Timer timer; - + private final int MAX_COMPETITORS_IN_CHART = 30; + public RegattaAnalyticsDataManager(final SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, Timer timer, ErrorReporter errorReporter, UserAgentDetails userAgent) { this.competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */true); this.sailingService = sailingService; @@ -74,6 +82,21 @@ public class RegattaAnalyticsDataManager { } public void showCompetitorChart(DetailType chartDetailType) { + // preselect the top N competitors in case there was no selection before and there too many competitors for a chart + int competitorsCount = Util.size(competitorSelectionProvider.getAllCompetitors()); + int selectedCompetitorsCount = Util.size(competitorSelectionProvider.getSelectedCompetitors()); + + if(selectedCompetitorsCount == 0 && competitorsCount > MAX_COMPETITORS_IN_CHART) { + List selectedCompetitors = new ArrayList(); + Iterator allCompetitorsIt = competitorSelectionProvider.getAllCompetitors().iterator(); + int counter = 0; + while(counter < MAX_COMPETITORS_IN_CHART) { + selectedCompetitors.add(allCompetitorsIt.next()); + counter++; + } + competitorSelectionProvider.setSelection(selectedCompetitors, (CompetitorSelectionChangeListener[]) null); + } + MultiCompetitorLeaderboardChart multiCompetitorChart = getMultiCompetitorChart(); MultiCompetitorLeaderboardChartSettings settings = new MultiCompetitorLeaderboardChartSettings(chartDetailType); multiCompetitorChart.updateSettings(settings); From c5e8b67da0807524968b6a784e4b59ff0ed48d9f Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 12 Feb 2015 16:06:15 +0100 Subject: [PATCH 084/264] implemented the competitor preselection for the competitor chart for series fixed recalculation of competitor charts after a competior selection change in the leaderboard --- .../client/i18n/TextMessages.properties | 2 +- .../client/i18n/TextMessages_de.properties | 2 +- .../OldCompetitorCharts.java | 2 ++ .../OldCompetitorCharts.ui.xml | 5 ++++ .../RegattaAnalyticsDataManager.java | 1 + .../EventSeriesAnalyticsDataManager.java | 24 +++++++++++++++++++ .../AbstractCompetitorLeaderboardChart.java | 2 +- 7 files changed, 35 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties index 8964ef11b9d..8ad020fd7fd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties @@ -1,4 +1,4 @@ -chartSelectionHint=Hint: To change the displayed competitors in the chart you can also use the leaderboard. +chartSelectionHint=Hint: You can also use the leaderboard (on the LEADERBOARD tab) to select the competitors displayed in the chart. chooseALanguage=Choose a language events=Events home=Home diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties index 97268494c95..55b7a72c686 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties @@ -1,4 +1,4 @@ -chartSelectionHint=Hinweis: Die im Diagramm angezeigten Teilnehmer können auch in der Ergebnisstabelle ausgewählt werden. +chartSelectionHint=Hinweis: Sie können auch Ergebnisstabelle (im Menü ERGEBNISSE) nutzen, um die im Diagramm angezeigten Teilnehmer auszuwählen. chooseALanguage=Wähle eine Sprache events=Veranstaltungen home=Startseite diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java index 0ff0c7ee412..0eefd9e57cd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -26,6 +27,7 @@ public class OldCompetitorCharts extends Composite { @UiField HTMLPanel oldCompetitorChartsPanel; @UiField ListBox chartTypeSelectionListBox; +// @UiField DivElement filterStateUi; private final List availableDetailsTypes; private MultiCompetitorLeaderboardChart multiCompetitorChart; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml index 5e29ee56842..2c1e97afcd9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml @@ -18,6 +18,11 @@
    +
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java index a1bf113a82f..8f586ba077c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java @@ -102,6 +102,7 @@ public class RegattaAnalyticsDataManager { multiCompetitorChart.updateSettings(settings); multiCompetitorChart.setVisible(true); timer.addTimeListener(multiCompetitorChart); + multiCompetitorChart.clearChart(); multiCompetitorChart.timeChanged(timer.getTime(), null); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java index 52669221b5b..a8ffde3dc02 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java @@ -1,7 +1,13 @@ package com.sap.sailing.gwt.home.client.place.event.seriesanalytics; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.DebugIdHelper; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -13,6 +19,7 @@ import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; import com.sap.sailing.gwt.ui.leaderboard.MultiLeaderboardPanel; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.Timer; @@ -36,6 +43,7 @@ public class EventSeriesAnalyticsDataManager { private final UserAgentDetails userAgent; private final SailingServiceAsync sailingService; private final Timer timer; + private final int MAX_COMPETITORS_IN_CHART = 30; public EventSeriesAnalyticsDataManager(final SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, Timer timer, ErrorReporter errorReporter, UserAgentDetails userAgent) { this.competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */true); @@ -93,11 +101,27 @@ public class EventSeriesAnalyticsDataManager { } public void showCompetitorChart(DetailType chartDetailType) { + // preselect the top N competitors in case there was no selection before and there too many competitors for a chart + int competitorsCount = Util.size(competitorSelectionProvider.getAllCompetitors()); + int selectedCompetitorsCount = Util.size(competitorSelectionProvider.getSelectedCompetitors()); + + if(selectedCompetitorsCount == 0 && competitorsCount > MAX_COMPETITORS_IN_CHART) { + List selectedCompetitors = new ArrayList(); + Iterator allCompetitorsIt = competitorSelectionProvider.getAllCompetitors().iterator(); + int counter = 0; + while(counter < MAX_COMPETITORS_IN_CHART) { + selectedCompetitors.add(allCompetitorsIt.next()); + counter++; + } + competitorSelectionProvider.setSelection(selectedCompetitors, (CompetitorSelectionChangeListener[]) null); + } + MultiCompetitorLeaderboardChart multiCompetitorChart = getMultiCompetitorChart(); MultiCompetitorLeaderboardChartSettings settings = new MultiCompetitorLeaderboardChartSettings(chartDetailType); multiCompetitorChart.updateSettings(settings); multiCompetitorChart.setVisible(true); timer.addTimeListener(multiCompetitorChart); + multiCompetitorChart.clearChart(); multiCompetitorChart.timeChanged(timer.getTime(), null); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java index b64a0a0b658..c6899dd8751 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java @@ -181,7 +181,7 @@ public abstract class AbstractCompetitorLeaderboardChart extends A })); } - protected void clearChart() { + public void clearChart() { competitorSeries.clear(); chart.removeAllSeries(); } From 2269ea30965426d0eb91b5b014a5ad974536df87 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 12 Feb 2015 17:23:08 +0100 Subject: [PATCH 085/264] starting to implement an access token generator --- .../SailingGWT sdm.launch | 2 +- .../Security UI sdm.launch | 98 +++++++++---------- .../com/sap/sse/security/SecurityService.java | 8 ++ .../security/impl/SecurityServiceImpl.java | 18 ++++ .../security/jaxrs/api/SecurityResource.java | 19 ++++ 5 files changed, 95 insertions(+), 50 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index b0332ffab3a..81118d7d4af 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -69,7 +69,7 @@ - + diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch index c74715a12d6..7750b7dbad8 100644 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -1,49 +1,49 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index b3840233f21..a934dd1710e 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -139,4 +139,12 @@ public interface SecurityService extends ReplicableWithObjectInputStreamnull otherwise + */ + String getAccessToken(); + } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index db50118e4bc..72554df2998 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -101,6 +101,8 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica private static final Logger logger = Logger.getLogger(SecurityServiceImpl.class.getName()); + private static final String ACCESS_TOKEN_KEY = "access_token"; + private CachingSecurityManager securityManager; /** @@ -900,6 +902,22 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica + " does not have permission to read preferences of user " + username); } } + + @Override + public String getAccessToken() { + Subject subject = SecurityUtils.getSubject(); + final String token; + if (subject.isAuthenticated()) { + RandomNumberGenerator rng = new SecureRandomNumberGenerator(); + byte[] salt = rng.nextBytes().getBytes(); + token = hashPassword(new String(rng.nextBytes().getBytes()), salt); + User user = getCurrentUser(); + apply(s->s.internalSetPreference(user.getName(), ACCESS_TOKEN_KEY, token)); + } else { + token = null; + } + return token; + } // ----------------- Replication ------------- @Override diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index a2199c495bd..f2dde163646 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -11,6 +11,8 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.json.simple.JSONObject; + import com.sap.sse.security.jaxrs.AbstractSecurityResource; import com.sap.sse.security.shared.UserManagementException; @@ -42,6 +44,23 @@ public class SecurityResource extends AbstractSecurityResource { } return Response.ok("Logged in!", MediaType.TEXT_PLAIN).build(); } + + @POST + @Path("/access_token") + @Produces("application/json;charset=UTF-8") + public Response accessToken(@FormParam("username") String username, @FormParam("password") String password) { + try { + getService().login(username, password); + logger.info("Successfully logged in " + username + " with password"); + JSONObject response = new JSONObject(); + response.put("username", username); + response.put("access_token", getService().getAccessToken()); + } catch (UserManagementException e) { + logger.info("Logging in " + username + " with password failed: "+e.getMessage()); + return getSecurityErrorResponse(e.getMessage()); + } + return Response.ok("Logged in!", MediaType.TEXT_PLAIN).build(); + } @GET @Path("/logout") From 3b97cb9fed2e1891b6d74328a093d1b05433e4af Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Thu, 12 Feb 2015 18:31:26 +0100 Subject: [PATCH 086/264] re-add mail bundle to launch configs after merge --- java/com.sap.sailing.server/SailingServer (No Proxy).launch | 4 ++-- .../SailingServer (No Proxy, Cached MTB).launch | 4 ++-- .../SailingServer (No Proxy, Jetty on 8889).launch | 4 ++-- ...SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch | 4 ++-- .../SailingServer (No Proxy, Jetty on 8890).launch | 4 ++-- .../SailingServer (No Proxy, Remote Debug SAP VM).launch | 4 ++-- .../SailingServer (No Proxy, winddbTest).launch | 4 ++-- java/com.sap.sailing.server/SailingServer (Proxy).launch | 4 ++-- .../SailingServer (Proxy, Jetty on 8889).launch | 4 ++-- .../SailingServer (Proxy, Remote Debug SAP VM).launch | 4 ++-- .../SailingServer (Proxy, SwissTiming Live Simulation).launch | 4 ++-- .../SailingServer (Proxy, winddbTest).launch | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index b71d3ef858d..10a790daf3c 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -30,9 +30,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch index 895c44256f0..5daca9029a3 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch @@ -29,9 +29,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 4b9fafe0b62..47e189fd705 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -23,10 +23,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index bff7106c857..5cdd0a51e6d 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -23,10 +23,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index 0d00afa2ab4..c33a2b9512e 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -22,10 +22,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index 52be241fa8b..b29f8efd126 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -22,9 +22,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch index 0ea337d7c9b..7154fe55d29 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -29,9 +29,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 2b681882737..6638ff75415 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -30,10 +30,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 0d13c3b715a..fe9ffeda563 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -23,10 +23,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index aecc68a6f33..187ee5a685a 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -22,9 +22,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index d2372ed03c9..02ecc596ff9 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -22,9 +22,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 7dac0013df7..43cc42e85e6 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -24,10 +24,10 @@ - + - + From d661c30de54b34b2b752cbb01145182ea0a30127 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 12 Feb 2015 18:35:59 +0100 Subject: [PATCH 087/264] added a text field displaying the number of selected competitors for a chart. --- .../oldcompetitorcharts/OldCompetitorCharts.java | 16 +++++++++++++++- .../OldCompetitorCharts.ui.xml | 5 +---- .../event/regattaanalytics/RegattaAnalytics.java | 1 + .../RegattaAnalyticsDataManager.java | 4 ++++ .../seriesanalytics/EventSeriesAnalytics.java | 1 + .../EventSeriesAnalyticsDataManager.java | 4 ++++ 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java index 0eefd9e57cd..bb66418a237 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.java @@ -5,6 +5,7 @@ import java.util.List; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -15,9 +16,11 @@ import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.gwt.home.client.place.event.regattaleaderboard.EventRegattaLeaderboardResources; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorLeaderboardChart; import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorLeaderboardChartSettings; +import com.sap.sse.common.Util; public class OldCompetitorCharts extends Composite { private static OldCompetitorChartsUiBinder uiBinder = GWT.create(OldCompetitorChartsUiBinder.class); @@ -27,7 +30,7 @@ public class OldCompetitorCharts extends Composite { @UiField HTMLPanel oldCompetitorChartsPanel; @UiField ListBox chartTypeSelectionListBox; -// @UiField DivElement filterStateUi; + @UiField DivElement competitorSelectionStateUi; private final List availableDetailsTypes; private MultiCompetitorLeaderboardChart multiCompetitorChart; @@ -81,4 +84,15 @@ public class OldCompetitorCharts extends Composite { } return result; } + + public void updateSelectionState(CompetitorSelectionProvider competitorSelectionProvider) { + int selectedCompetitorsCount = Util.size(competitorSelectionProvider.getSelectedCompetitors()); + if(selectedCompetitorsCount > 0) { + int competitorsCount = Util.size(competitorSelectionProvider.getAllCompetitors()); + competitorSelectionStateUi.getStyle().setVisibility(Visibility.VISIBLE); + competitorSelectionStateUi.setInnerText(selectedCompetitorsCount + "/" + competitorsCount + " competitors selected."); + } else { + competitorSelectionStateUi.getStyle().setVisibility(Visibility.HIDDEN); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml index 2c1e97afcd9..11201dfeda6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml @@ -18,11 +18,8 @@
    -
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java index f80497147bf..169f4dd0a5b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java @@ -181,6 +181,7 @@ public class RegattaAnalytics extends Composite implements LeaderboardUpdateList setActiveTabPanel(competitorChartsTabPanel, competitorChartsAnchor); DetailType selectedChartDetailType = oldCompetitorChartsComposite.getSelectedChartDetailType(); regattaAnalyticsManager.showCompetitorChart(selectedChartDetailType); + oldCompetitorChartsComposite.updateSelectionState(regattaAnalyticsManager.getCompetitorSelectionProvider()); handleClickEvent(event, competitorChartsNavigation); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java index 8f586ba077c..9b0590d8434 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java @@ -81,6 +81,10 @@ public class RegattaAnalyticsDataManager { return multiCompetitorChart; } + public CompetitorSelectionModel getCompetitorSelectionProvider() { + return competitorSelectionProvider; + } + public void showCompetitorChart(DetailType chartDetailType) { // preselect the top N competitors in case there was no selection before and there too many competitors for a chart int competitorsCount = Util.size(competitorSelectionProvider.getAllCompetitors()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java index 3d48af5335a..be1c4184f59 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java @@ -212,6 +212,7 @@ public class EventSeriesAnalytics extends Composite implements LeaderboardUpdate setActiveTabPanel(competitorChartsTabPanel, eventSeriesAnalyticsManager.getMultiCompetitorChart(), competitorChartsAnchor); DetailType selectedChartDetailType = oldCompetitorChartsComposite.getSelectedChartDetailType(); eventSeriesAnalyticsManager.showCompetitorChart(selectedChartDetailType); + oldCompetitorChartsComposite.updateSelectionState(eventSeriesAnalyticsManager.getCompetitorSelectionProvider()); handleClickEvent(event, competitorChartsNavigation); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java index a8ffde3dc02..35f0d588f16 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java @@ -125,6 +125,10 @@ public class EventSeriesAnalyticsDataManager { multiCompetitorChart.timeChanged(timer.getTime(), null); } + public CompetitorSelectionModel getCompetitorSelectionProvider() { + return competitorSelectionProvider; + } + public void hideCompetitorChart() { MultiCompetitorLeaderboardChart multiCompetitorChart = getMultiCompetitorChart(); multiCompetitorChart.setVisible(false); From a1a8166b0878e4832e0d5e0732252faa73069b2a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 10:18:32 +0100 Subject: [PATCH 088/264] fixed MongoDB bundle reference in launch configurations after target platform upgrade --- .../SailingServer (No Proxy).launch | 76 +++++++++--------- ...ailingServer (No Proxy, Cached MTB).launch | 74 +++++++++--------- ...ingServer (No Proxy, Jetty on 8889).launch | 64 +++++++-------- ...o Proxy, Jetty on 8889, Cached MTB).launch | 64 +++++++-------- ...ingServer (No Proxy, Jetty on 8890).launch | 62 +++++++-------- ...ver (No Proxy, Remote Debug SAP VM).launch | 60 +++++++------- ...ailingServer (No Proxy, winddbTest).launch | 74 +++++++++--------- .../SailingServer (Proxy).launch | 78 +++++++++---------- ...ailingServer (Proxy, Jetty on 8889).launch | 64 +++++++-------- ...Server (Proxy, Remote Debug SAP VM).launch | 60 +++++++------- ...Proxy, SwissTiming Live Simulation).launch | 60 +++++++------- .../SailingServer (Proxy, winddbTest).launch | 66 ++++++++-------- .../mongodb/DomainObjectFactory.java | 1 + .../userstore/mongodb/UserStoreImpl.java | 42 +++++++++- .../mongodb/impl/DomainObjectFactoryImpl.java | 8 +- .../mongodb/impl/MongoObjectFactoryImpl.java | 2 +- .../src/com/sap/sse/security/AccessToken.java | 35 +++++++++ .../com/sap/sse/security/SecurityService.java | 15 +++- .../src/com/sap/sse/security/UserStore.java | 10 +++ .../impl/ReplicableSecurityService.java | 2 + .../security/impl/SecurityServiceImpl.java | 22 ++++-- .../security/jaxrs/api/SecurityResource.java | 2 +- 22 files changed, 519 insertions(+), 422 deletions(-) create mode 100755 java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index b71d3ef858d..f685cbce3fa 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -1,38 +1,38 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch index 895c44256f0..c7cd8286754 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 4b9fafe0b62..744ede396ed 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index bff7106c857..3497d1f5ce3 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index 0d00afa2ab4..c1ccff764ef 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -1,31 +1,31 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index 52be241fa8b..89e75a62324 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch index 0ea337d7c9b..2fe280a7389 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 2b681882737..b836d38a0c4 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -1,39 +1,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 0d13c3b715a..5d38ab12ded 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index aecc68a6f33..e8302eddd65 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index d2372ed03c9..4e2a4b6a51b 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 7dac0013df7..bd9c37f73b4 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -1,33 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/DomainObjectFactory.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/DomainObjectFactory.java index 59f4cf7a7ed..29ecf730a5d 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/DomainObjectFactory.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/DomainObjectFactory.java @@ -15,4 +15,5 @@ public interface DomainObjectFactory { User loadUser(String name); Map> loadPreferences(); + } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java index 46c3a062db5..6f6fd62ce5b 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java @@ -29,13 +29,20 @@ public class UserStoreImpl implements UserStore { private static final Logger logger = Logger.getLogger(UserStoreImpl.class.getName()); + private static final String ACCESS_TOKEN_KEY = "___access_token___"; + private String name = "MongoDB user store"; private final ConcurrentHashMap users; private final ConcurrentHashMap> usersByEmail; + private final ConcurrentHashMap usersByAccessToken; private final ConcurrentHashMap emailForUsername; private final ConcurrentHashMap settings; private final ConcurrentHashMap> settingTypes; + + /** + * Keys are the usernames, values are the key/value pairs representing the user's preferences + */ private final ConcurrentHashMap> preferences; /** @@ -48,7 +55,8 @@ public class UserStoreImpl implements UserStore { usersByEmail = new ConcurrentHashMap<>(); emailForUsername = new ConcurrentHashMap<>(); settings = new ConcurrentHashMap<>(); - settingTypes = new ConcurrentHashMap>(); + settingTypes = new ConcurrentHashMap<>(); + usersByAccessToken = new ConcurrentHashMap<>(); preferences = new ConcurrentHashMap<>(); final DomainObjectFactory domainObjectFactory = PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(); mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(); @@ -101,6 +109,20 @@ public class UserStoreImpl implements UserStore { } } + @Override + public boolean setAccessToken(String username, String accessToken) { + final boolean result; + final User user = getUserByName(username); + if (user == null) { + result = false; + } else { + result = true; + usersByAccessToken.put(accessToken, user); + setPreference(username, ACCESS_TOKEN_KEY, accessToken); + } + return result; + } + private void addToUsersByEmail(User u) { if (u.getEmail() != null && !u.getEmail().isEmpty()) { Set set = usersByEmail.get(u.getEmail()); @@ -175,10 +197,24 @@ public class UserStoreImpl implements UserStore { @Override public User getUserByName(String name) { + final User result; if (name == null) { - return null; + result = null; + } else { + result = users.get(name); } - return users.get(name); + return result; + } + + @Override + public User getUserByAccessToken(String accessToken) { + final User result; + if (accessToken == null) { + result = null; + } else { + result = usersByAccessToken.get(accessToken); + } + return result; } @Override diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java index 147863f9173..9f6ef354951 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java @@ -33,18 +33,16 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { @Override public Iterable loadAllUsers() { - ArrayList result = new ArrayList(); + ArrayList result = new ArrayList<>(); DBCollection userCollection = db.getCollection(CollectionNames.USERS.name()); - try { for (DBObject o : userCollection.find()) { result.add(loadUser(o)); } } catch (Exception e) { - logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load events."); - logger.log(Level.SEVERE, "loadAllEvents", e); + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load users."); + logger.log(Level.SEVERE, "loadAllUsers", e); } - return result; } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java index fe9e9ef3feb..d94e600e0ae 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java @@ -46,7 +46,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { dbUser.put(FieldNames.User.PERMISSIONS.name(), user.getPermissions()); usersCollection.update(query, dbUser, /* upsrt */true, /* multi */false, WriteConcern.SAFE); } - + @Override public void deleteUser(User user) { DBCollection usersCollection = db.getCollection(CollectionNames.USERS.name()); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java b/java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java new file mode 100755 index 00000000000..ab826487576 --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java @@ -0,0 +1,35 @@ +package com.sap.sse.security; + +import org.apache.shiro.authc.AuthenticationToken; + +import com.sap.sse.security.impl.Activator; + +/** + * An access token as issued by {@link SecurityService#createAccessToken(String)}. The user name which is returned + * by {@link #getPrincipal()} is obtained by looking up the access token using the {@link SecurityService}. + * + * @author Axel Uhl (D043530) + * + */ +public class AccessToken implements AuthenticationToken { + private static final long serialVersionUID = 8528031991813216585L; + private final String accessToken; + + public AccessToken(String accessToken) { + super(); + this.accessToken = accessToken; + } + + @Override + public Object getPrincipal() { + SecurityService securityService = Activator.getSecurityService(); + User user = securityService.getUserByAccessToken(accessToken); + return user == null ? null : user.getName(); + } + + @Override + public Object getCredentials() { + return accessToken; + } + +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index a934dd1710e..9b9f71b8c14 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -140,11 +140,18 @@ public interface SecurityService extends ReplicableWithObjectInputStreamusername can be + * authenticated using the token. * - * @return a new access token if the user was authenticated, null otherwise + * @return a new access token if username identifies a known user, null otherwise */ - String getAccessToken(); + String createAccessToken(String username); + + /** + * Looks up a user by an access token that was created before using {@link #createAccessToken(String)} for same user name. + * + * @return null in case the access token is unknown or was deleted / invalidated + */ + User getUserByAccessToken(String accessToken); } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java index 4dd17e4947f..fb6d6bb98d0 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/UserStore.java @@ -13,6 +13,8 @@ public interface UserStore extends Named { User getUserByName(String name); User getUserByEmail(String email); + + User getUserByAccessToken(String accessToken); User createUser(String name, String email, Account... accounts) throws UserManagementException; @@ -87,4 +89,12 @@ public interface UserStore extends Named { */ void replaceContentsFrom(UserStore newUserStore); + /** + * Stores an access token that can be used to authenticate the user identified by username. + * If there is no user by that name, calling this method has no effect and it will return false. + * + * @return whether a user could be identified by username + */ + boolean setAccessToken(String username, String accessToken); + } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java index e248a93c404..fcc2bea54db 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/ReplicableSecurityService.java @@ -19,6 +19,8 @@ public interface ReplicableSecurityService extends SecurityService { Void internalSetPreference(String username, String key, String value); Void internalUnsetPreference(String username, String key); + + Void internalSetAccessToken(String username, String accessToken); Boolean internalSetSetting(String key, Object setting); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index 72554df2998..fff7edb9959 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -101,8 +101,6 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica private static final Logger logger = Logger.getLogger(SecurityServiceImpl.class.getName()); - private static final String ACCESS_TOKEN_KEY = "access_token"; - private CachingSecurityManager securityManager; /** @@ -309,6 +307,11 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica public User getUserByName(String name) { return store.getUserByName(name); } + + @Override + public User getUserByAccessToken(String accessToken) { + return store.getUserByAccessToken(accessToken); + } @Override public User getUserByEmail(String email) { @@ -892,6 +895,12 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica return null; } + @Override + public Void internalSetAccessToken(String username, String accessToken) { + store.setAccessToken(username, accessToken); + return null; + } + @Override public String getPreference(String username, String key) { Subject subject = SecurityUtils.getSubject(); @@ -904,15 +913,14 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica } @Override - public String getAccessToken() { - Subject subject = SecurityUtils.getSubject(); + public String createAccessToken(String username) { + User user = getUserByName(username); final String token; - if (subject.isAuthenticated()) { + if (user != null) { RandomNumberGenerator rng = new SecureRandomNumberGenerator(); byte[] salt = rng.nextBytes().getBytes(); token = hashPassword(new String(rng.nextBytes().getBytes()), salt); - User user = getCurrentUser(); - apply(s->s.internalSetPreference(user.getName(), ACCESS_TOKEN_KEY, token)); + apply(s -> s.internalSetAccessToken(user.getName(), token)); } else { token = null; } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index f2dde163646..68f2c82889e 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -54,7 +54,7 @@ public class SecurityResource extends AbstractSecurityResource { logger.info("Successfully logged in " + username + " with password"); JSONObject response = new JSONObject(); response.put("username", username); - response.put("access_token", getService().getAccessToken()); + response.put("access_token", getService().createAccessToken(username)); } catch (UserManagementException e) { logger.info("Logging in " + username + " with password failed: "+e.getMessage()); return getSecurityErrorResponse(e.getMessage()); From 06f0cb2874c069751593850090d40f75daa1012d Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 13 Feb 2015 10:27:00 +0100 Subject: [PATCH 089/264] added i18n for competitor selection in charts --- .../main/java/com/sap/sailing/gwt/ui/client/StringMessages.java | 1 + .../java/com/sap/sailing/gwt/ui/client/StringMessages.properties | 1 + .../com/sap/sailing/gwt/ui/client/StringMessages_de.properties | 1 + .../com/sap/sailing/gwt/ui/client/StringMessages_ru.properties | 1 + 4 files changed, 4 insertions(+) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index cbef88b3ee3..de1844cb017 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -34,6 +34,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String carry(); String competitor(); String competitors(); + String competitorsSelected(int selectedCompetitorsCount, int competitorsCount); String defaultLeaderboard(); String noSuchLeaderboard(); String leaderboardConfiguration(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 6a71c26d303..5f519f6e1c8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -29,6 +29,7 @@ total=\u03A3 carry=Accumulated competitor=Competitor competitors=Competitors +competitorsSelected={0}/{1} competitors selected. defaultLeaderboard=Default Leaderboard noSuchLeaderboard=No such leaderboard leaderboardConfiguration=Leaderboard Configuration diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 5fc7d30cba1..e2736b04394 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -29,6 +29,7 @@ total=Gesamt carry=übertr. competitor=Team competitors=Teams +competitorsSelected={0}/{1} Teams ausgewählt. defaultLeaderboard=Alle Ergebnisse noSuchLeaderboard=Rangliste nicht gefunden leaderboardConfiguration=Ranglisten-Konfiguration diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 67d2e6e8c6d..7bccf80bf6d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -29,6 +29,7 @@ total=\u03A3 carry=Суммарна� competitor=Уча�тник competitors=Уча�тники +competitorsSelected={0}/{1} Уча�тники. defaultLeaderboard=Таблица лидеров по умолчанию noSuchLeaderboard=Така� таблица лидеров от�ут�твует leaderboardConfiguration=Форма таблицы лидеров From 348fcd64b0c9c2bd5c7c08a2ca08a50a0bc09ab6 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Fri, 13 Feb 2015 11:35:05 +0100 Subject: [PATCH 090/264] Added parameter to only execute a single testcase --- configuration/buildAndUpdateProduct.sh | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index 31784594d9b..28a35bd0944 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -95,7 +95,7 @@ parallelexecution=0 p2local=0 if [ $# -eq 0 ]; then - echo "buildAndUpdateProduct [-b -u -g -t -a -r -o -c -p -v -m -n -l -x ] [build|install|all|hot-deploy|remote-deploy|local-deploy|release]" + echo "buildAndUpdateProduct [-b -u -g -t -a -r -o -c -p -v -m -n -l -x -j ] [build|install|all|hot-deploy|remote-deploy|local-deploy|release]" echo "" echo "-g Disable GWT compile, no gwt files will be generated, old ones will be preserved." echo "-b Build GWT permutation only for one browser and English language." @@ -115,6 +115,7 @@ if [ $# -eq 0 ]; then echo "-u Run without confirmation messages. Use with extreme care." echo "-v Build local p2 respository, and use this instead of p2.sapsailing.com" echo "-x use this many worker threads for building GWT permutations (default: 2)." + echo "-j only execute the provided test package during tests" echo "" echo "build: builds the server code using Maven to $PROJECT_HOME (log to $START_DIR/build.log)" echo "" @@ -151,7 +152,7 @@ echo SERVERS_HOME is $SERVERS_HOME echo BRANCH is $active_branch echo VERSION is $VERSION_INFO -options=':bgtocparvm:n:l:s:w:x:u' +options=':bgtocparvm:n:l:s:w:x:j:u' while getopts $options option do case $option in @@ -172,6 +173,7 @@ do u) suppress_confirmation=1;; v) p2local=1;; x) GWT_WORKERS=$OPTARG;; + j) TESTCASE_TO_EXECUTE=$OPTARG;; \?) echo "Invalid option" exit 4;; esac @@ -535,7 +537,7 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then fi cd $PROJECT_HOME/java - if [ $gwtcompile -eq 1 ]; then + if [ $gwtcompile -eq 1 ] && [[ "$clean" == "clean" ]]; then echo "INFO: Compiling GWT (rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.*)" rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.* GWT_XML_FILES=`find com.sap.$PROJECT_TYPE.gwt.ui/src/main/resources -name '*.gwt.xml'` @@ -587,6 +589,11 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then else extra="$extra -DskipTests=false" # TODO: Think about http://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html + if [[ "$TESTCASE_TO_EXECUTE" != "" ]]; then + # http://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html + echo "Running only testcase $TESTCASE_TO_EXECUTE" + extra="$extra -Dtest=$TESTCASE_TO_EXECUTE" + fi fi if [ $android -eq 0 ] && [ $gwtcompile -eq 0 ] && [ $testing -eq 0 ]; then From b32730a9024ec24f81f9caaccd4424f00b5f8ae4 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Fri, 13 Feb 2015 11:35:46 +0100 Subject: [PATCH 091/264] Updated configuration for com.sap.sse.gwt.test --- java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF | 5 +++-- java/com.sap.sse.gwt.test/build.properties | 3 ++- java/com.sap.sse.gwt.test/pom.xml | 18 +++++++++++++----- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF index d62d8cf0d7b..7fbd22023b4 100644 --- a/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF @@ -5,5 +5,6 @@ Bundle-SymbolicName: com.sap.sse.gwt.test Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Require-Bundle: org.junit4;bundle-version="4.8.2" -Fragment-Host: com.sap.sse.gwt;bundle-version="1.0.0.qualifier" +Require-Bundle: com.sap.sse.gwt;bundle-version="1.0.0", + com.sap.sse.common;bundle-version="1.0.0", + org.junit4;bundle-version="4.8.2" diff --git a/java/com.sap.sse.gwt.test/build.properties b/java/com.sap.sse.gwt.test/build.properties index f11210d3f2c..0a97af7a804 100644 --- a/java/com.sap.sse.gwt.test/build.properties +++ b/java/com.sap.sse.gwt.test/build.properties @@ -2,4 +2,5 @@ source.. = src/,\ resources/ output.. = bin/ bin.includes = META-INF/,\ - . + .,\ + resources/ diff --git a/java/com.sap.sse.gwt.test/pom.xml b/java/com.sap.sse.gwt.test/pom.xml index 2d43815d42e..b22d0bb4c1c 100644 --- a/java/com.sap.sse.gwt.test/pom.xml +++ b/java/com.sap.sse.gwt.test/pom.xml @@ -9,18 +9,26 @@ com.sap.sse.gwt.test eclipse-test-plugin - + + org.apache.maven.plugins + maven-surefire-plugin + + + **/AsyncActionsExecutorTest.java + + + org.codehaus.mojo gwt-maven-plugin - 2.4.0 - - **/AsyncActionsExecutorTest.java - + ${gwt.version} + + **/AsyncActionsExecutorTest.java + test From 0800cab32a754c90e5725f0cea9911a3652c51be Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 11:43:50 +0100 Subject: [PATCH 092/264] started with access token / REST security; test case read --- .../META-INF/MANIFEST.MF | 1 + .../security/test/SecurityResourceTest.java | 52 +++++++++++++++++++ .../userstore/mongodb/UserStoreImpl.java | 9 ++++ .../com/sap/sse/security/SecurityService.java | 5 +- .../src/com/sap/sse/security/UserStore.java | 2 + .../impl/ReplicableSecurityService.java | 2 + .../security/impl/SecurityServiceImpl.java | 11 ++++ .../security/jaxrs/api/SecurityResource.java | 21 +++++++- 8 files changed, 101 insertions(+), 2 deletions(-) create mode 100755 java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java diff --git a/java/com.sap.sse.security.test/META-INF/MANIFEST.MF b/java/com.sap.sse.security.test/META-INF/MANIFEST.MF index 104dfefa28e..a911893a977 100644 --- a/java/com.sap.sse.security.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.test/META-INF/MANIFEST.MF @@ -10,3 +10,4 @@ Require-Bundle: org.junit4;bundle-version="4.8.2", com.sap.sse.security.userstore.mongodb, org.mongodb.mongo-java-driver;bundle-version="2.13.0", com.sap.sse.mongodb +Import-Package: org.apache.shiro.session.mgt diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java new file mode 100755 index 00000000000..f2d13ca7661 --- /dev/null +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -0,0 +1,52 @@ +package com.sap.sse.security.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Properties; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.session.Session; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sse.security.SecurityService; +import com.sap.sse.security.UsernamePasswordRealm; +import com.sap.sse.security.impl.SecurityServiceImpl; +import com.sap.sse.security.jaxrs.api.SecurityResource; +import com.sap.sse.security.shared.MailException; +import com.sap.sse.security.shared.UserManagementException; +import com.sap.sse.security.userstore.mongodb.UserStoreImpl; + +public class SecurityResourceTest { + private SecurityResource servlet; + private SecurityServiceImpl service; + + @Before + public void setUp() throws UserManagementException, MailException { + final UserStoreImpl store = new UserStoreImpl(); + UsernamePasswordRealm.setTestUserStore(store); + service = new SecurityServiceImpl(store, /* mailProperties */ new Properties()); + SecurityUtils.setSecurityManager(service.getSecurityManager()); + Session session = SecurityUtils.getSubject().getSession(); + assertNotNull(session); + servlet = new SecurityResource() { + @Override + public SecurityService getService() { + return service; + } + }; + } + + @Test + public void createAccessTokenAndAuthenticate() throws ParseException { + String responseJsonString = (String) servlet.accessToken("admin", "admin").getEntity(); + JSONObject responseJson = (JSONObject) new JSONParser().parse(responseJsonString); + assertEquals("admin", responseJson.get("username")); + String accessToken = (String) responseJson.get("access_token"); + assertNotNull(accessToken); + } +} diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java index 6f6fd62ce5b..28deb3c062f 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java @@ -123,6 +123,15 @@ public class UserStoreImpl implements UserStore { return result; } + @Override + public void removeAccessToken(String username, String accessToken) { + User user = usersByAccessToken.remove(accessToken); + if (user != null) { + // the access token actually existed; now we need to update the preferences + unsetPreference(username, ACCESS_TOKEN_KEY); + } + } + private void addToUsersByEmail(User u) { if (u.getEmail() != null && !u.getEmail().isEmpty()) { Set set = usersByEmail.get(u.getEmail()); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index 9b9f71b8c14..43a3bcab1e4 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -141,7 +141,8 @@ public interface SecurityService extends ReplicableWithObjectInputStreamusername can be - * authenticated using the token. + * authenticated using the token. Any access token previously created for same user will be invalidated by this + * call. * * @return a new access token if username identifies a known user, null otherwise */ @@ -154,4 +155,6 @@ public interface SecurityService extends ReplicableWithObjectInputStream clazz); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index fff7edb9959..73bf80d6d7d 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -901,6 +901,12 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica return null; } + @Override + public Void internalRemoveAccessToken(String username, String accessToken) { + store.removeAccessToken(username, accessToken); + return null; + } + @Override public String getPreference(String username, String key) { Subject subject = SecurityUtils.getSubject(); @@ -926,6 +932,11 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica } return token; } + + @Override + public void removeAccessToken(String username, String accessToken) { + apply(s -> s.internalRemoveAccessToken(username, accessToken)); + } // ----------------- Replication ------------- @Override diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index 68f2c82889e..f98fe131b91 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -13,6 +13,7 @@ import javax.ws.rs.core.Response.Status; import org.json.simple.JSONObject; +import com.sap.sse.security.User; import com.sap.sse.security.jaxrs.AbstractSecurityResource; import com.sap.sse.security.shared.UserManagementException; @@ -55,11 +56,29 @@ public class SecurityResource extends AbstractSecurityResource { JSONObject response = new JSONObject(); response.put("username", username); response.put("access_token", getService().createAccessToken(username)); + return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON).build(); } catch (UserManagementException e) { logger.info("Logging in " + username + " with password failed: "+e.getMessage()); return getSecurityErrorResponse(e.getMessage()); } - return Response.ok("Logged in!", MediaType.TEXT_PLAIN).build(); + } + + @POST + @Path("/authenticate") + @Produces("application/json;charset=UTF-8") + public Response authenticate(@FormParam("access_token") String accessToken) { + User user = getService().getUserByAccessToken(accessToken); + if (user == null) { + logger.info("Invalid access token " + accessToken); + return Response.status(Status.UNAUTHORIZED).entity("Access token " + accessToken + " not recognized") + .build(); + } else { + logger.info("Authenticated " + user.getName() + " with access token"); + } + JSONObject response = new JSONObject(); + response.put("username", user.getName()); + response.put("email", user.getEmail()); + return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON).build(); } @GET From 8a4b71e7878e9c6b2f5aaf8681f93b7d6fe8fae5 Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 13 Feb 2015 11:45:22 +0100 Subject: [PATCH 093/264] fixed positions of checkboxes for highcharts legend (grr... I believe for the 5th time or so) --- .../gwt/highcharts/client/BaseChart.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java b/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java index 74201014580..cbffc78b2f6 100755 --- a/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java +++ b/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java @@ -2198,6 +2198,7 @@ public abstract class BaseChart extends Widget { public T redraw() { if (isRendered()) { nativeRedraw(chart); + nativeAdjustCheckboxPosition(chart); } return returnThis(); } @@ -2903,6 +2904,24 @@ public abstract class BaseChart extends Widget { return seriesList.get(seriesIndex).getPlotOptions().getDataLabels().getFormatter().format(new DataLabelsData(nativeData)); } + /** + * Fixing a bug currently in the base Highcharts implementation regarding the placement of the checkbox in the legend. + * Requires the symbol padding (see {@link Legend#setSymbolPadding(Number)}) to be set to 25. Invoked + * whenever the chart is rendered.

    + * + * The implementation moves the checkbox between the legend symbol and the legend item, using the space made by setting the symbol + * padding to 25px. + */ + private static native JavaScriptObject nativeAdjustCheckboxPosition(JavaScriptObject chart) /*-{ + var series = chart.series; + for(var i= 0; i Date: Fri, 13 Feb 2015 12:41:25 +0100 Subject: [PATCH 094/264] ignore failing SecurityResourceTest for now on master --- .../src/com/sap/sse/security/test/SecurityResourceTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java index f2d13ca7661..80fea90a3f8 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -11,6 +11,7 @@ import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import com.sap.sse.security.SecurityService; @@ -41,6 +42,7 @@ public class SecurityResourceTest { }; } + @Ignore("On master branch ignore this until it's ready and green") @Test public void createAccessTokenAndAuthenticate() throws ParseException { String responseJsonString = (String) servlet.accessToken("admin", "admin").getEntity(); From b07fd851828d1e75c9660214ec4523d07104da6b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 12:42:47 +0100 Subject: [PATCH 095/264] upgraded TracAPI from 3.0.7 to 3.0.9, fixing a ConcurrentModificationException --- .../META-INF/MANIFEST.MF | 2 +- java/com.tractrac.clientmodule/README.txt | 36 ++++++++++++++++++ .../com.tractrac.clientmodule/lib/TracAPI.jar | Bin 753694 -> 765744 bytes java/com.tractrac.clientmodule/pom.xml | 2 +- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index b2f04cadba2..b10a81fdeaa 100644 --- a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF +++ b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TracTrac Client module Bundle-SymbolicName: com.tractrac.clientmodule -Bundle-Version: 3.0.7 +Bundle-Version: 3.0.9 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ClassPath: ., lib/TracAPI.jar diff --git a/java/com.tractrac.clientmodule/README.txt b/java/com.tractrac.clientmodule/README.txt index 31ed42bd96f..08aed822c98 100644 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -16,6 +16,42 @@ It contains also some files: - test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example. - Manifest.txt -> manifest used to create the test.jar file +******************************************** +************* TracAPI 3.0.9 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 13/02/2015 +Build number: 9904 + + 1) Bugs + + - There is a ConcurrentModificationException in the IConnectionStatusListener subscription, when a thread + is adding subscriptions/unsubscriptions and other thread is sending events through this listener (Reported + by Axel Uhl,07/02/2015) + +******************************************** +************* TracAPI 3.0.8 **************** +******************************************** +This is a final version. It fixes bugs in the implementation and it adds a some features. +These features add methods to the API, but they keep the backward compatibility. +This version provides a new JavaDoc version. + +Release date: 07/02/2015 +Build number: 9826 + + 1) New features + + - Adding the method IEvent.getEventType that returns the type of the event (e.g: Sailing, Orienteering...). + (Requested by Jorge Piera, 01/02/2014) + + 2) Bugs + + - The changes in the static fields of the route (including the metadata) were not propagated through + the subscription library. Following the same approach used for the races, controls and competitors, + a new listener IRoutesListener has been added to manage these changes. (Reported by Axel Uhl, + 06/02/2015) + ******************************************** ************* TracAPI 3.0.7 **************** ******************************************** diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index 57e8e32f66f003cded89ac7065d5f779aceb25dd..f3d5016f5d8701a8b5eda0eedd0cce3e1c4d1112 100644 GIT binary patch delta 99301 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 {&w157dpY_yk*i9~K= zlDRPhGRFD2A0>7F0HZ0<1wc?j6VveOGh*IUiWz$N0tH?saR9JUVHPcuC-v+s?f#k*ZGC+C_%3GKKX>bvZ#1E$i-~+Y_G)m(V8ihM`e2L+I-HjEj7E=jO(8B{E%E%fP zMIBr+#KeRWt_xxCKpdFLjI3jj;K(v!N;o|bV#7MRHV{Z@+xUyN9EflD&}BfY+{Dsq zdO$wi%lL9Uho+tSjn&TgH@(DvqbIRws+HUGVKrcEP|;_dvTK^m+yy8HA5=ThRd55oiEzxEewHuOgo7mA3t$mvaH( zqYQx}v9L@qq>d?K+V_PKhrxN?bwMH`F#@LgeOT;@U}zuKWj(<_q`Cw)8RJe*31V6p z%7Cy9T5~;>jH3HI{+c8z{ zV4>SX@x_l0W=OdAicm&=wOn};9jHXHcqYl%>eBpOMoi9Kg!rq5F z9f=R+43Gfb&K!*(59cBw7ED$CTtVTz2o!V$K@OoMdGH_-XUWB@jnvZse;eQv2DO`K z(Ri}3Pb7YAB{33+(RRQlM>-JG!3&XiBX-cVW;cuxux1oK@HiI*l2tBZvs8j)jQiX+ zGW9ip`-p*H!ccM0AInM;KunE^Qm~a@HX6W$1fpp|a9JQ|uw^5-x)ou8ItY$v5vceTA3{4!eSP)Km)qoVf-QUxg0-Q`-51)o&Gn zF)%R3fB;f0{VH@8zbsXX(T!#&TLT&%p=a;=FOB!2P($@ikY92P(JCB$Zj=zp~ zVh);iqX3Pr3HKKeQ(y)=Y(B;tfcB;k+pp-}fc9amjLeBs$_Lsmg3mz$t?kROtob(} zJ`C#s-;{3|XgBD`_7V;OHsx5>pEn>LtT664Ana@nHaWZoRGq!h+ETLk>3d*jBbdg3 zhyVWdck(fsp9ijr!*}j4(J&z$SQt#_PJI(%!<6AbzuAy9a9JAAh)^46_}ESSfD~~P z04zO21MtI-ZsKe206ICb2a5~FQhN9uh{-TkKI{+{_XkDT2CxZ-bH08ojN>hQ?-hCr zz|0N&r5@cN2yFf&;mczJ%`h>DW%xY^5&XT@IfH> z@Lyg5!^sTXo&X(y={^xN!#2NgijqX{_pbzeKfs4@T(X+o0BmI+mpcaybKcY z{h~KI+4(CPTpVry$rwA2lE}bRpnGTr-2WP~(4p?^m$=fk}`ArcT6tGjezb%-aU=gfl-6xGf3N!#bWm8K_mYf!0t zgl&@XDu<$Jo?Cl*(814>@ufY9rZxXW=aYuN{RE8*?ItXyMhe75%1TB87sLb5Ybg*X zMj^*wIx$+Hdmm7U(AHz{lN3k`;}B$xgZB%dwKy7<137iSBV3qtX&AAOgcaW1Gnpn05E*zeCJVw(=72xccu5%G-2{jR zqXNtQA9DAA@p7P`j}h3#%ZGz(O@nx`4x9vNB=Y;=wsc^xWIC?wX{Qt(P61Otqb(tT zxF>{mjWnE)j_ZEyD^4|}122995(&Ms06$5Gv@k26M|2NeNr(G`Ay(2E36fNe*?rgu z9FhbzZfkG4`O}^n*e!`=Xv=^QlK)u;orJzip*x=m(2o%|3z36+xj13_gCrD~NEB-$ zR|fD{i}W4?6>OaWiDP`#POT|X0}P}C77&Ir!!mpP2eB}#XqZLWy<{#}=nx6xKPx|I zcL5bKe}q9ag#sLziI04nGlApJVNxVu`z(kIKFvc-kD0DqZlCml?YyYjNG24~IWY9U z)=Ehp9EGQHL8RZ91u&XIWq)OOik~@$TriMpo>l>oi0BC?dgb`P&GM3fz4upF` zM*bauz^jT)R#hbtfUAS>tqeUeC8^NB(&%#_=6{T~Jbw8k6OhyjKH50rvHwwN?qw5# z9UtI2#mtRsH|_%lLBOw1z#o5nk6#^Y8(KQ)BP~#MZAigG68yl9Q)Uj{@Twd@WY_+i zIIcrN1@GoS^q3Mh?6S-Jzo*O9I{(SVgq|>CnmSq|d>MiX(_eoH@MJEu|KC+|^gN&V z1DYs@)!Oh9SX7bKg&FUHPWWsd-j!Z?z?C;}$q#fv|FZBA*T3}bDp{aA=vB~e3476e zcr_2-iyq4d08A!mTsb%~ACH@dPQHXo=64`rfk_H*jXA^VHz_-?CJ*#;r~fjAh^WdO z3xnYg!}I&~-*D>y{6%20LddUX`4@lwEBtydZ6S~yV)GZxw-6^=fqx`a6kz@SN4Bor zU;GPzAL};aB7n7MkEIzE;g?a6=wuqFy)0y~wiEE(#5~S-p~g?TrGW2vL5D@C^9h*Y zE@XoR_qhvDzFokg)D}am@W&opg}i>{@s=Sw5z#creTu+Irvvt+GQ)bs_`chr7+^8S z?_r5zMU119`;xH95+#r*rXd=8wo`}$H;{pTi%>lf(_%^zQv{Dn@NO6^0WfpvI9e&# z%|#C`uwE%h-p=|fi;)v4CH$ZiKUTg%GiVq7#UOJJR}{=8pNz&qF-(Fml5o+ArWnmH z4m12Bp@*~Y;XE?YsAV4xbbbi(5OyA>sOc=wRszyk6=R0EYEB7ACg>V#YLaGWno>J78WVD3X%|1)dELRN!|*B`QH) z-vKOcP$hmpxd5F!I)s5tufi!dktotS0_vbIsDp%vXGgHY0|6_xAmgh5z`b`^+_86H zfM2e{>-z^y`}Q82#jYBsuiekMOH!cd<~aUq_Gvh&8aj=|ova3VXXns)HDR?HTv1AK zE`3AKN27t;vla(MN&v-91>_MZH(*YJfN5)Sn`swG zD_I+XFO@;hMK~-cQ~t%@LP^R9->k*kiCONfh8gQ1nSaKU@tBL_reJJ9??{~?(3qMU zgP5*MLJlV|lCr@Dbr7!Yh13D#r)mB}=cgs5g+JEer7okH`04+{WJeEF`_+I8@oASu zhBHA)MuSN27=d&bMuLpou=@i%FnS_Q>;p{YEbb=K8u>owO8`k05aUCF$ zWV2(i;3QU38Z5&*cJw!(DyGx8F$Ufp4Sb*kG|?i^;w|TYbA~qHcDxCt}iHoj>e098)#64Jn#j{CT}b4W8;u^s@v0VP4WHj*OxSEh~!__-ry`KU@9 zOB)a;rG~{CaTdLVdFU1a7CER}It0prH-cd5l|2enF1QijOsX4!a;^veqS2smt&OIo ziA@mbH1`jdmdqZ%JjRi7<&FGHfHf5iXoO6*n~goaG?nRSQzYq3&1a$@J3Gp z8mYe#>0j~A$|DH+x7*Zs1Q_4^myr&p$il@wkw;Uq!hoM6!AFC@-=(Jifj@xlKhR76 zq$y^kAVg<}PdDSEEPF6#CLPBm-)@GmPj-OOQ)yCLD=ha$Gk*P(;W0?A!X-bmBIU!3 z)0k)stpq3u%pYNJ%x;6xNF09j7+(TCXdGW`x-9%B2cp4*$BuR@X_)Ysbp9+Rj}&Z| zi(kn{pWYyO0%D6^Nc3NiVHUes&ta&rP%FfUwOXkal!h-ZIieLG5@Txe14lHsB)rp# zR||RqxamPG(hK(bd}l$E1%ogn{2~=s%YT~>==m^#=hK6k6Jc-L?y{GO`ma!87Y$ex zjFr8vrXeeQmhbTJo`DHUP8;8PL<8#rYZg zpY4@Bej&K>3Mm~t(2lprryVF@dt*-lYS?HW83p{j9X}FJq8VO*Uo>y= zkZhWQPR4m+FWUIdlA`VnO8W9Y!|O7Lc+fCFaWJ{=#8(=nPJlHG|Ahs-ia3OQdgOn% zRiYWw=9~a}9zfS3FtexNFZ7O7TxTI9J0o!fxbZz`lt&2o^Y3E$d+&l#BH}6DBMDD| zHY)e9G}n8i% zNy>n+09$Sv-GISr0L}2f8Vtc@-S{r34b31ujAl@Wy@pAtU}bxp`Ex6me4YXq9tQom z5rHdBNA@H#!wJvv+w^tM0an^5p)gAL!zdUT#5?g0mwf@aBHp374#CJ5xTVjTPcLD0 zP;Szo`V+>iCKFg;;S;#B!CFS!17LQi&@^?}tp{I!BhksqvwJ!)!{H#@!VC^f%ZG*- z06Zld5z%o1duZm-{KlAGy}+arLI6d^LBXFO;EIQ%dLeVH^!L3$4785b?C?4%czopE zAuFSofOh`Jo(h;=gAy+P0o>5`67PlqG?(!X76H>j5_iC;gl52KOIJaXi=NGkfHqE8 zIkbNN1&yAYVfeqA&UL&3mEHtcM_5|Vh+yMBNHT4OOc?gShaCN#rBBL1y&D4E2Vpy5 zK%m*Rv36$l0WJCHVU2#^W%YK53HG5Uql2Yfant7nMcN0SfVb>H{7Be5f5ON5ahv&} z^Be`}uFVHHpODFz9gCdEPDTlD_v1_t58#tWFV!spP{P5CYahlG7<8SOo{I)n@4#iM z5#0201wxL4OjZQtR?qjB`lp}a#}7=OUmyri4MLLtM5URBs>?$`4Il$YijEPW{lw;g z^9G^)m4owTaAdg@Mj8yBRmuiu4&mEiEt)nVy+;#; z`G@hB@&=L;%e|=pCSX9Tir|vF$K|lmi0Rce-6jactSTKqhDS{e1V!>Wf zQADw%Sg;Y>{EecRr-?CYFvV!Bi7|gmY_UWE0edH4#r}R%?(QA-eQ&?-1~aG5nKSLo znS0NxU2PpLUW9-PExvJHoF}chfYR47ytL~DV?*W{g{j}%qe+5En5pDptSXkH{(3>p zI4PwZc68^(sG$U+TCcj* zC&$9x92I78Cmxs7r51}lB@8C%{}{t98f5x$t8K{`i0DF47Q77o-831lFEOKgBjAn zp}Le2(8Ck`N8T^5$JhVVRJGqOLyHMBX?2A+QLSJz_Hz?|`JeG5xmffiTA(7qK$>3xMe-nL_uAQ!?Zv7- zhsGm8>V*kpU^y%n;(RrHV0Geq)fmo@zF$%__!NL0U}ZTeYz0MXKz=B&tI5K4%z(8# zfB1bX(CvXP2fz>QB7Rqq^<28k&fVf@wasSF+CPKL6J&Ba>j~1VL>+0fN?_U>KXN{M zN{sxwoTQi*7yrbI%l6ngkhEQ51vp^J>J^mda#&2}o4Q+rKetppZO$tNucBW#tf*Ao z#=WA&hxeFB+*v{-AOBOz06VbP1alz&^-)A>AKeq!6#Pog-7q#-b4?5oPV>D-eFcTK z!pWLQcDLR~F@wnn7lkLny*Pbh0bRl70u_F%`Mo>9nA*oo8$Mww8GKV6vd3P7#B;w> zU;uGBXy-x>KUH1fB!#{GgAtOb!*+Ge$K^*-{I}3H`wEX7`nUZr$DeXt&CJTX4mRls zAyE+pN9<~8L|EUzHoushy@6DKdPnVyB<_YPeE>iF^Mk#!Tovd(LS9lR-m0(0%}oqYvM zeg}c_w7%v7$A{UBr1XrPLc(sT+PA+2hR+bXqH?ltsgn$A;F?!x<0Dz=mO4)VORF*-l|=i%c**d3am(LFEo$kBc?1xI5}e_c#jk zDbc_nPfvclqi!fpQ-oEe8iX2mRi6*N3)sdo4U7>_zAIWMphIl(H5hCP3?|Pj^{#7( zps52n_FCmyPPq=f!4bYJK{d@sC~RNqeviYP9~c#~^qy*hynCP+y}q{$;YygwX4eJ1Ssv>AQ2K+rKf@+qY8iHR$tMCK1pQ5ez79_zfffqqeuZ+{s>w(d(MkTS7Hf7Q>D43S)Q0bW}R|l zpv5}nd}5!4dm$hMO3A%FgHiC_9+Z3Q@p)j;+(=pVT%3%TW5H1L1j}r5DueaE(C5@k z3TsGeJhO9Ky;RMR6?->Ld(yLLo6)r>EPyx$30ve8FPlu0?9wnh$p+jfP+vORJCox#g*Udm z(=Y!=EC}0Slkw_x7|rZ)dN)Mu!5*;fn`hD^Y>3?~P$ z|4Ggmo-sUT+;HN0*XY6Azp?%J&{C|VlCh?jXWEy3Twr!xdxea15fvKwiTgZy;CLA8 z#Avff{41g*!>?s2HGU%TT_xl_zB)1~8T9o~!NwB(igsLHc$mGB3^vWklb*un?K*Q< z!Be$`&eGz4y7OX}XJWD2c~Ej6H<)@SY9#lY`MnvcJ=2aPzE#IynjCNQdJqM+;2ksP z894Vyr6s}SjLO;VkC82M(F=}2Z#lAH7|T&xKUed}Q=fx_?G#>I*595lA~^Tc6qcI7 zVQo|F>zNlAd-^zsbizh@r+4-4Jaan7@0)7x&XQSJ)6EY4p=4+XJ_bFtdSx4ZPVocC zvT^o!FD-^gF56fI*$=(OJiVYwFHWwxxV3faI*GjGI>Z{$Osyd zsw$Bb`FrY!wDv$f0{87Gwe0+3ly`Tg^uy2|VPU{np;T8o;v~fiAo07P^uar#L#!AV zFk&rm2YFVT^}(1r&gbwUyhwoDi;3Xk`@PxIsU>|Lg610mU-#mEctL2 zxoAUQNDL=bP=;Y7@wGZW%y|t12Rxz0P1rooRC3&y`U!yUE2xX1SO$(%<*-A{)m7sO)%`xX|G-6qyqOXc$d z<7C=r*8NeIMPD=oc(~E(v;F$7e^1eh+UQ_2l!aFNGX(ib@ z|I|20pNL1E;LbJHiUF0I`IZO0>l(SaiOy=`uE(ok7h4evw3>=kkBInW+-kNT2KdsW+wwB$w_ zNzy6q~g`*M`HKqaE^K=PN$GebRypaJGfL>?}a17D$nG`C3JiEd)3f#lcU zbhXGjwR;m5{3>VYg#CZCXdM0eTv$VVa?AuQb8}trJ)0VBw?L-z!@Gprwd}sC2n?>F;}OY!}71{bms8Umq)g5 z1B*9QsKhGWjBX$goE3h#yT#kixuw9Pd8zV7D(7Dldp*gysB&b>P zcp$wtt12@0Gx*d&jpSAG3xS%0g&J8=OX0T~3Zv(I@C$T}#3&@s znzi3@)_vU+@lFo5Qvbk;Tx+QKl1UGAcEtIX&fcPXmpo7|pM(nGP@yyant#ix(fXTh zR5W8xjIU3u_SUU2emcnytSN6F0W-nN~gLOx< zGWgdRJDJExp4}C_iN%#Se)jd3w}3}LDPN{pL9Tf#ekLOriT8b-rZ%U?J?=RZglqB0 zwRxiBB9lB6k&=<^0rmgUn>4W}2R+n5ou-c3ZgSAGb;`=au1=(&r@@Yd)mB{CS|z8@ z^Q%wLdbP1WkT?1^h^*1YjhSd!Ic7Z?B4Cs^#~Tgl&@KZHCO*yYzT^KvX}+Gys3|(c zvR^Inm;X8Dq$j%`>)f0Cf2$enBs4$A8jPe4S?Z~FY#N5J?ZwNzHE)<;E6RM7CqK~z zlHfX`GLG#oJ(vNGtMSO;wx=97N^_H#EtW4jgGZIFp@o`j&V+{!!T7fT)1BOMAuh;_ ztE+V7PM1;_8a{PnTq4O%ycU3$AEf2aYBAKNM5(8UmoKn)lw9(~fPLsOy!ZQhiqS%L zXl~_!uTX^|Jn|se-h-LQo6mxX#Pk=!;wP-t7S=8o`oB zl^R}VDrd6TOA&7sV5{N~Z{GZ*%u5j$N8fuv)aROVXHvsP&*M!@)MKlZev=_86HjYA z=3hm9GdE|FRjI3CUi`B7+i_p&_<^KqylO%A_7X2YR=mbX?^u09ytHgZ4h@Ub^fq~P z%`y?Wk#HjvJnbWnDB7M4zDB2(O@bPeH&Dx`odv- zpqU18+{uCZs`97mqcc~AsBneMa@N-~$8Ih!+`m2nJoh%= zJp+-6r}Z_+IDcWh52NN41Y&yVf}So9GF4qTer70)Kv#fl3K}@?a@0|1vl<4M$Nx_g zO#Gcms|Jd=(ZX`Peof>spPqVy#gx)Au>cf!>+S4N#jX7a*G*159Ktq+PUVL#F=a{B_n@sO~U?Dq|#B57JCT2GTuH zY+w2eI9}Bcesu)xDvzX>$C~hQ;u)wOmonWG%6`G(FaPuTJc`QSt>hVZGM4xT(TgdpG?b)`(EDhZe-Q-cD;H3TP*P`{ zo?m2VhUmFD{0-emy@MEe0NI(t@jEn9&kzST0-KyAyx1ij_N1#cHi=FtT{}D+ZFd?r z?I@X}*?TEZq}QeM9ze7uRb+s5*YI6gr;dhVLG)8^99^j=N8>=%)IxU`i zkQWE%>HV3TFjsJz9^?=JSMWkDyGds{_qvoIKSA)(aQQ#+UN%P-m@Ou~O>uB62rv(@mzoiJgs^u*deurex zh5dUbLa#h5CCar)SDj?4eMPx`@C77)HK;f_R3XFGMMH2-tV?>Zu zbHWvdHfYOwt;E~ft|m`g`!P_*Sv30sH;USotN|+b>m9A2Rv!8AX1O0l6?eQQwSk5Kfn3;GTZ1Dx+(zx`XDMvHfaSL`_>-1VYBVhofn=M6U=7`d zC@}@pA5nOLW_C@4QwW}ti#~k36|?&mVkfVjNnAMH4Tl)n^9t2l)7tSf(}EyTb=61g#j&u5I2J_$%_{F zu|4-$4~9~47D{c1=J-uJ8~jLkTj3Q6wIi(jfj( zA1XdV)}>aaOoR|wZ$tH$O+C#d zm0jT9Wt~;6*vqC`&Y;Dfmf>Rd&Z$`p>P5!Is6?}3P|ZI@@$kL|XEG~Rv`orJ8@6S^ zSVlOVd<+*liwf}~Nr{SL6VQyD+brJ4MokI|_VX2g`5&7(l*Hd+O5C0_`1k(+<7c>S?xL7pUE1F5^jj2Vf8y+eNL?ObQz@kHhxD7_904PI%QK z9L;_ld$~|awFRG0RPnyiuvic~FQO$uBt2G~+ElE2JK_w8M#06JOT^)enH+3xY7%(GzckogNqC+h*We{oJsx? zgFRW^RqYTv@GqOGD<~U=z$0jbT&Gn9gV$p4lGknR*P2jWNXAEEU%K0CebsW{_QjG* z4y#^XXToQ)vC7Ior~mgOwI1)m!{+=Zjx~Lz!QJdWA;AX{u3|yn4>OP4Ela-9aBaHD z;7U$(Q>W#!ZeSQ$U_!tE9rp-W&OFvA-I{LdRu6`x*@3blEB`PNF~c~JS_x_uLK46= z`@b3l)G>hMgDZE80*cW3ga+aFED;ZGl)hz>8=4{qO(A<}vvbsuSf3No^7KTo$-Bgh zzfBbTjlHhdhS66=(DysFkX&^)-XaGMYr3oFOQGGd>qh9Sr_wZ}S{lQ)|Jm8Y3-iWz zm_eG!cqlC z3^hqek-c2@u=R|KRIdeqkD=PCgr~Ez5q4siW8lq*r z#M0Eie@&YwaDghgfNc9i0UG=@Zw+o{&2sCtZu0~JLeb(KCBLcPNJBK?V}&0d+nioI z?jzveM&FR}=QY;gUsds=4p>*xrMx#fsH{|LysysGUM>y}ZX~t0+RhVuqrKcCWCSPj zLUGWfF)n?7EiV(QEQTs_rC4Cfk)(&=g``#ahY3M0dkv9eKjCtZmp(kT4+;zdjU3}x6>U<$kxcKW@HY~b_41i}5%?`F z@PF*6!7ozrT}Msm5P&xN7arOKbmrf~4>>-a@!53G4f(nsDmXSq@anHPk?8(nxw*4HaPf>sIWpz^jSB+*3X#WFXU zWs6od>~s_A{|7afJLi=iCVUqX9*pmhPkb$!BV)IH+7l4%15u2*?)X=5FB6dm`8)(J zY9jhH`<81coX-<}Ehj}S|JX$2OmYW`UDjvzwF>8=7WWWOl#4Lt*4pyTg9cc>c(WM&I=MPp@-#}rzay76UCKyY-_-rMIl?*|< zL=92_I*1LO;3t{F2*{$Dh(cEi<9F?cv&HGkE>Qga<#*r3W4`i&y|vR{K>x6(lq;kK`Fe@Fp# z0e!HsmlyM&k?65XeO`%CBgDF3*vOr~FM^e?SUCNvlT?NmLCr^-6b@egjss6+2^a(&y6gua9RW-u(y^dx(vx|C!S)z9ZSS>iSa%jBG$DVgy@ z;Rl(}u#?nBj2AeW&nS^q()an$B3cppZZEm_UcJasSw+IOJ6N9RWOu%JXeemMUfgi={w? zUgO0tM~jF!aT){0Geq%$UmX0%*e}E&es99Z+i3FWPZqVDbjzfv6WKRL?da?lMVq@6 z*3e=I8gp(_-9n76so2EGG5=+ZLcDn|cP#Wd|CsS0f5V5^@Ot5|ML#!!!KyGd$Q8Zu zgt(4VS~IMay>CBX0gSiK)!zx6R>c+GdfI_+%PkJ`gQccOuriY0KEh3fyW>o(_5B3>UY>b8n^V*_BrILbp_MXwzPuFAf(oxXyKTovy__`54j=Z*f=qM|gr#9*?uNV21WP?US`oT>2MB|SN8QZPR5WSTvPM%S#OsrNLo zUhdY7@lxdsMRaR+(tWWHuU5)5#esa8p(xyKN7iX#0}gFq#2CroP5YYg-RZ}c#I;SY zY0zd%e-n%!nDpE5NA2sVI*rlFY|@69P~Az%G?fkAv)k+)M$)G%VHSog|0QGFUohf% ztm&d9rQFjfMIuwZ

    4ngWaF%*0Aq-A7TZwxnLB!r$AhJY8MU2WU>N)$R0jELE>L znGaNMZkl3gyL$>7O+r~Ll$XEG#1P~%m6bPV;ZHUMvCk-x)EO9pKAXmX;rN1+Cdzqu z25LHJI!T&?a6{V!yvxKVOPyH4vcU8CbR)TzjJH{2XNiq(bmGC2)F)}(BW|zP}_=s=uJc9$6f_Xr|JfW4q3RzzbPSpf5?BrG)YFxwL%Q zOi_+6GX;_U*`iOnw=WzR4=onskpukAW)YWcMSPTLOg1DwnMEbGqu*-MlM~>;*%tz4 z`=4yaz6r^oAChW2TLs3?24VDUUIU+AMsEv$=d;-%rF4pPNr$vb8?=&2!vX1(egsrHRY1anNT+luh)9b9iZmh$ z+=p`xuW$U;_s9M2buE|A?3vj$d(YmpXO5LKf=_}4N%Xa0aH=!-`1og>{5@5ZNTlHC zf3-!CE-%=pv;3-zujx0WPkyUWtDY3@(fYNX^{mG1!~Mv!pEu>U1`v;YJIR(v&OaPX zTd<;jBw+6PLY0=SsOV#+!wagcCX&p!Avw0ret+kkN^-+jT*K8)KM8*i$eM;{dR$nK zkM}q-hzXU{Xqlu=vQY7VZHRM#@9Jpn{XC{LD;tTpVuLe7O1TkHjl*{7HtuEyUZeVp z`;_&qA3l+ODPJi$yR)@;TRZR#e5i;!r-P!j8|mgT`CHuU$d4mC^Jao?Sqz{_@jTIt z(&JJtmCf@V0M}l#dnNF^8s5d$WsB={;YT0u@d?$-|MnAdliNBupO>n*CP_Gj=mCFbI)FY9aL;WNKF^y7z|Iim?Z zbLO9#OnC(nMyoPq4T8}rkYfyE0u(Rf5d(Wl(6i`HfOHC%H{}VG71KK2z~co1LlHzN zljPU|xsg~Np}I-MU`G*ch?x?c4FYL_7VjMq{_M31y9Lr)+!fYQ<2bC_UtR zQNk%k{029M7T~7lp|zz+JyW~PO+&SOpNf;_mInmX8Xmqz?zuLPGiT2Df`9+1MG$Z% z#b$v%C6Sg3EfQLZDXetFz)}SqtD;95P~H>)IyTIJ7rrpw>Hs=i7)AvVjPjU|H;iWJ zk819-c~A_fpZWSfg(K>Jt`q;ig(JEfdUzIgU+%X`-Ty(#V$O8VqNPv3*Uz`OSL{C1 zd~(A`c!e(fi#I5X|Gy=KsGgVrQ%WK~dCEr?tU$s);3p886d_hDq*aOF1VRh!MIk6s z(pZbp0sxzv=z+8;#2on50c_VG{{gpRBprzotm5>-ik&2^T;0H3f@uLG@6o7ffRMMM7*bWPH}+0l9_ZMV~k~ zIBJn)ZvBYm=(`kiv#VXQyrKJw)Vi+xj~Vavz#r2MI>^KAZtExq*kk8>WsaF8A9<Mlz(S15OaU0QS>f1Rg~!0EsnbeYIn|e>fa01epNt9A&{bF_qObo97i-#kMBTl zuvu)C;*ePH^NS~Z^>hMp&MW){Z$zH1XmXU=di1)Kr_%5Co2=4vWtmWZw1MOg)}@Ph zn3a_+Ex1HI9T_bv=WFMs6}nY(t6b)egT@u&a_bIk%@$^@40R@B zEin>kd0t6)>HajXu{D#4nA%+H8+((-2JKHcIVOP#EqVuOPc4ha-j}i3tIz5q#tt4f z2k1npMs-pLgq%~y+Zm_1mG&svlvsktOgxHDy=u8UNBYh=4c&51ijcca4v&q-J~*&G z2#XHiN-YW_{P1G(8d5S;i#yjAs;gcS?aU!{ST_7Pjs_=l_579ExcaYDL3W;N?QiJ> z@Tra_V@1kIDa*^8WGt*uMV}ot?=kL<{46`F9K7e3s|?#`_av07V02>0tV@o0FjqIy zaI@i@38@orDN)hejkv|Bw0^g_`3nIL%BwbPk&<9J5!&!drp?yk~M%qpWW1p#^-Sizwp%PB|4`Go~`IiCZuI#Frc&| zq^!a&>Ak|KzUY*Wygis(*)wGi&_CCv{#g3(iYQ?~2e;19NAtU+T`_w;{f}M-D)EQ( zT$H_&CONMyOn!fU)vCvf+a0pUgJ<|_Kz-oq$X)K6AwHC(5V4P>_4YI*1p_Ni(q7k4 z%o#5p-M4*jls?ZW(b|0&$L=L%>t{MiSfDs?{)PfXD~qtxtq*ypUdD*`s>+2Q?+vuw zm@acDM=lFDSB9Spjhs)jewxdFC#~;uL8q&$x6_BM+d~4j(^YGe?y+QO%BoJ*Gxt?pJ_Hl|9tX# zTH5#ychA#Z6Yt~M8tUgQjLM|PORXQZC+3f=dxbpBaLx1rC9EJwtMePY8%`k;!LDTc zYWrVlea5C-%$CSwIK6>-hTJawW1|uE)Y63 zcL-%ULyPkSDg%8z=NQ5Q0pNn6av}CD4-f%~W*hX9jGwz%qvv}zZd>_>zehFoEIu?3 zn7`q7zc~wzyTd6L{zcX=GXWl(fp9MW_54S}ano-(WZLfR3Nkw3dcb{m#G+>$W$saP zf*2jGa8qktl>2vN`IkkRSGnwxrk{#pK3XK1^LjX|rt@w!#C$YO`f}xy#uAUykaaG} zt8GzL|K?&r*<18uwr2Vvp=!-^jGHXDxxL*6lMnOAZAEs2B6{zE%97)o*)%BF5y-r3ab6 znR_oBe@u6cD<9v|9g+CJwMtT#wXM9-Hh%3;OJMe_W#N)}fJ&dLK$F_%OdN~$0ov^+ zc0pQIEZ0dX=vCNpEMilJ8$SK`-G_vnB!#~+C0_!#XU-&${5vTG(5cBuynY}?0?ej? z!4>y$=ig%EU*VVt&`k%E7sfmeZ1SKwv^z4PINRzM|m*&0uIx`bR(|t4tAu% z9y+xm2dn@D0pKhP`72ow#DE$A1XXo*$4-pL2!xd84cZv|O5zKAKraJ9TYatZCN|A5NQIy$ ziB*%epTo+Uq>G2a&7Fy0sZQ(;!0-+xR3|9#fQOj~nric2Dl7_Y1_eLqt1cP3i{D4%?8|AH2X^-0$D{xM~W+0`|aQv7n5R1U$EKr_AlaQ~DRME7$^s@Bw<+2(oIP zMS83#3SiVq-X^JbUAm1;s%F2=W3_XVV<&GD1Nu4Ol%utI%=)FweiZ_q=OFN^>9@PE zBSFl(04oYSu2Jp2#FCf8VM^YmN;%#|VE`wlnY>E^ROW()v$=N@o7lE8LU^n1AL0Lv zA0V+ELJC;sA&6?bAbS`RD3HVgHb+LwL70yqL(SttUSKDl;6b$zK!!2xl-U#BNl?v1 zkRuW_11NAr6e)%m(4jqX%Y75QM0BeTc%} z))R2M4tgZQ5W6t|^TJSGfJ^~G01THQh=A-;1QF_a0)x>sR`uh9~4gT zIG2IS_;nS6gB2Ge8aN#ISOzYSkrm`OW)8SNFrh#^CRia(&RG~%DxM;UQDlyg3-}l% zpPUjf6r>t|eUc6Pu8b=z8ODX%1oRNAnIJk_*l&kJG1brCJ7sYxqWaK@jyQr}l; zSZk8K0Fm4i&oYrXi`^!EI#R+A*nOZbL2=$hAk5{`rC^}KG~bphC3l&>O_uh*~o zcsw0@rg?bjtwpAkrO8V-?{F_@vt-7`k4G0m4UPmR%Y2@^{aWsT;yQdUtEQsOXFb`w zM5VdS{Gj8uwgGUEl=|FcLMky9nQnKX#;N=s192(6S+_vNWhMjDh6r90>nK>OT#VV* z1JS(mOA+M;`X3tg8DK0T5=zc;$jNG$t+3A8e%lO^->k7MQ>HBc&NVNneUOc9I*gl8 z^Ok^4n|NY%qI$^u48FvAwd#3jeO?F`Vc#6Ci^%OqZ*fIGGTnEo9Vg*9a@2|Nxv)G? znAAHcyk~Kc<`-2i+;5AAIzzrqei^a$pvXkclSn91!HMy@yIu}G%h7rhD^dkD0-x5q zTN8T3#;4{*K@)HEDXr(h$V5L&$P!^T*)OuL0tX#7y+ywIHY>`Pv5s4Vsv{3u=~-)_ zrfNgi`u8sWAfRO(3m=MVj@%`VF7D)rWBRPXZh9`L)#q&Fj>6HEbjgBkffr4L)wkFK z6tpw9p789oWVYjs3bQOs5@qnyss7Nu z@<2zV`r3?rgh(D=*bVE0M9;xTlfR~Bn;r#0lzI&%~OT@atFESNm|tK~wn$?c1Invo9K z#;h$lcXTR0kMm|8@+(*6a;S||%D|7DO1Y=sWl3x=)(85yi#kTeXJt7dw4)EVV1d%p z(yV05qLKGP(&W?(6eOiY3d|2b=!baSK+4%!@ew(sw+?-`Yhf1a)zqC8Tm6EVJz2UD8ESxoXDd3qrZ zDnB-8bn`nq|J(RaEV~nbHiST+^GyX8&Pg^J>UEmJkw-TD^yIt4c#NDsYuY8Wg z^_|@2rq(&{^Ejq$MNUe7;WMXu{42()C8-|r<>zXjMu<#feYuu~WxX1_96~h4AJuFf zXojjZe2-SYIU#XJ%V;El1G=W|bPqb+CkG6;K&k_!8cOr1-KnRBpWTr0DtRAG6EYBc z*AU0G)Q53hxZ^c1g@e&1^URe^h`jM8lDz!sK3(?4lqE08VpL4vMKwp0&GXXdUhvAd z6H{j|;&PdF((W6o^3=mg1X@*w__|GStV{P9X7LNs8M#Bw*`L2Q9FyPEKP(}4Ic>tQ z(Ctz6q=I#UKaEgExNb;!g({(^;}f|T*N3Cc>XNPr+HEU$1lF>2|BUEW|Gt2H`wY3H zTK62+!Rhf^>ah&|LphPHgB$T9lm(%)hKShxmW;bdpuW}}sjjw%-veo+( z^I3d)T3|~MU&k3&YSb!ih+~Pj^~R@t_rOb8{O=oQh_xi~rgI_rkEB#;CL6BIe*psT zNhs`f3-{A}q%d22`O}@E*O5QXb3JhdX(sr%crfAS-Gx$%11KX)a~tn(3r=ss@<-#4 z+l4|Ax;s)Y=*`J4#hFV5wszEI^<^+f56FMt_9(K`^<;v3+cTu>?BG#-fE+A(l5P5L z5OaTTRd|0Pvc<`kq?JBwVNFbbK{tf{Z6~86ht7?p)(-yFFFAubY(bH?>|Wge9D(eb z<2T*9e<2T5@a~Ia+}Ib=DD_~|uThxL*VQUCf1@az4Fadzno-2Rr$I#>8qynH%49YU6gm$bVIsWe{S@j zy+P`$#<`{RjFai}NC;VVW4#Htd}x#N>}0;7ct@GuMV36p$+c}F)kp8@B|pdzv%#F#dEU~DCV8mOp5-~wy8kR43Q zg-)GG^C9NgL!sN^Nv2K(CWDYJ%m{RT?@)svM-`Mo+OgRw_IQ9wtAI2TVa8!VBq^>- zH~>p60)a|wfb3vd|LcqZylR7B4+$qQ)Z=!D8J6Rd^H}mWIqG~D#0^X3ggKxh7lDTw z?1h+PfG0-~l*JGv4V#|n;ebgjc>G`lq(>m-e-nyk6cSC2X|qm(>0-+w_Ie8dr-%T; zbzu4`^$Eg@#RKO1ARYu156Jlp!2`0FApMxh=%TnH9Lfk3e}){OVJFEWkoXP42uy$Z z$1sov6>4S~l7!6=PfnC416MGp(1e5QLBefo5O(YsaHTMWQS_S-bF4DZneYiVK))41 z3|PGe4<{YlkZmlp=sX|O-3X@8KX)O4*oh~}bvh%I4OO=fA;wTZC(Ea}h)}q{A@taM z8_f?|Tyr>d9nB*Ogx$vCIw@M5s10NSqyn@wA}CR$_|U~anil~;X@~)#CUA*l5Kz<~ zf*=p9C@~V~bquK!9zYNSYKR6(hF#s0qQXlM_Agzr0A|e4L5#xCx>0OK&;dd%paue1 zplw**KsF`Qe?xFlNOmYT_y3!mjo?XUn+poTQaT+BShOPWQF6S{%0G+#9Ye_yywXY+ zfQq17C+iC~r~#Wc@GKA{0M*1&Mf20#hTsMmg`sU2g@LCiDlEE_qr&Mta-gCeL4nc} zg|=Y?OfyID1Bw@*=P?9>%wZCMa|d`zdwv0|*zD67va` zUU&gTWhfkFCI`iOD{!)(oS+~Fg1Zr$?S&5;cJ-z#NXMW21zbi2Sc(1FC z(bdUr2Ds%xX#p0^)B8!V-eQcAO8ZP6?If0KNIAZ2~9^f#j~HQ6uVl8FUFN2)d4<_y)@Uw~Jy>I0e9U9CXiv z=g^fu^$WBb2mrQB)C-w)<8M`Cx!{8|c)Ih>JaC^E?4_zU~EdL)0 zci%uou@p{$^eE^c)CYqX1p;sUn*?xY0u)qs40>AEIh{#?YIq0T!)pJemI|OHQ-2Qz zcEkrLb}#!gT6YRcfIa z8Y~Wg`~a$M8wxAIs2kiaG3$Vuz=L6HE*Qs0JHQ44#)Yv0dP3M3*1xYOgI1C=g*Xc+ zegrpbbz&Gctf0U&2i!wWp7a3Vc8hf)5C;LkIt@BY2RW=7s{k;Az;@fr{No`2XgA^j z2%|=^(7>>}6L2aR?MV+C>X`PFB7jyTnpN~1AqLnqc5@>;3sc1M0-6*WClxR}3vN|; z%&-Fj443HZ^wa5};mjdOQLE=*16WB<@*>Pm5PSiK3+(cq7S%B-SOj^o<%1EOl0oa` z1lTD6i~ctd_&yIBMyW7N?%zEqX)zcHRxf`^iUKKilVP|7jY1qa;Q$q`4%5VrJZ0e= z>by4W;=h1ws9HT(4}|<=DNfST(?4P{t|)~=+wswgIdyu^<#OeHGps#qP8x zIUxuF60gB{Fr&dNf%~LMi28O7CV^SK)1XZNn3=;cPo7X<z|C0+s{piJ&fSDzo_e8} zZo$g2QlagqvI8cHVz!4ppuo%o%e#x%GXl7S3jrG}@N=m3V3;@7Y6M8()TLV)jlQdWq^go7q8My8MWk-=cgmqwD6Ybe(0>}L@N&xvC^d9~^SP&K~ zx)P1eMurM2fsOpx1f>CxQ9{&{a+nKN7bg#KfKv(-D2rN{ z81|k4@eIrnOYL-_(by5F zs|&C-EDAL9=*XV41UpR}(9EAQLx8ehh6!V{doYIlr36Yqa1TL%+T4I~V9B5tZ_XD+ z2t3$BFr(adVAz`h6v+8kEX98=73$dmEDt*!%?moIDLsO5pECFkCJi*NAed0#0dC{( zHw);+SltKL)0O~^y^BGC*7+Wb?w^E!wV$9(SW?1|{&%hV1?uK-5)xjp*s?f!Sv3E6P|^&qf;H>0Vh*X{Cw~z zEHY613uqv)@f*PekP5*kF}4IM?@tE}N>&)|jLAOGYb~q+CqpgC!OO9}hPL%TJn0{S z3Yt-d^ZZ@xKi#!Q;8lmE8oUr|Vxae9XrUA};NBQ@pJWc0?FJNv6-BNOzmHuU^s)CZ zWNG+>B&be!xIOXB~%=0K%20tFK=P*FpE9!1sRVv3eY=gNp#x_=HpdMHRda zGx_A5F9|S=PlyLFSHp)eL%@>GF6Ik;IzUs(L4e~D!5OIYwQxl&{y(_T z-D?Dd#Hi|*a8YdcpV??q40Z5I3|f#cOo0yg7lxCUk^p%lR6iVl{`T?y2^zap6e<-^CW}>;u&g=k(oZ zHJ?%@mG$xH2)M*VXS}t&G0M)3D?@3L7**LE)5W4u=pgwxPtj6^-8SNZQIrzb<05ii z-0ipemvNgykyU#<$<)(4*C*Ms{gyaKZ`8X5n`{!1zb|ug<@wGblGtMV>Dg15%9bMY z2f=i=`l|6DsF_V!^hU>p4IR`HZkw6d|>>FJ^;AA>mOL5xJnv9Rjp z4^0yjHj>XY$K+|&Ohc9A?KZ>Sj)W2s`nb%|;K#Cie5^tyJHIy3K^1B#GpmGAX=c6~ z8vq|LJ?T`3H5U&6d)k-U@gjcJNa^vKv6MOQmuxs)pkls1zAwAD?wo!=7Vq)1pXgYp zoFXe$hK&5vg0lL)Id7Y>bBe@oC7YNZFZ1o{l*BAe8|^FIkG`Hii+A(ls7dqiq%8w##>ru%Y73_j7s$14t`$8N|&?`O7=^NH60ZcB5$9A_|TklkTb;Dcy ze!~KHO>XC7B;MUB@=5m8H}YY+RS)JwCPkNTWnVfH_$@k=^G(c0&r4>jIT&~|I7jX1 zdq{h7g?R(ssh$j8VcOvTJ?}=lH0Yoh3r3N(!+&6Xg}fUsf|Biok6_v;uO)X9)PS44oCY3im?}d`fB^1Jpecd2X&_p{@1gG>c9C9E-Gsfo{mKcdL! zSJp+(M!8SI|H{lxe7TYw+FX5AbTNs(fba1_9O+kpUls~-(b-G)q$FwgR5ajqvPhuXcS0` z90NpwyG#Wp{R9i@U>IM4V}5`ED6PRS0KwFRc&L{v@Jm<}V1NZ*53Rrn(LDeQHAq$C zE4=G(>H;y)pY)Og*S^6&VKf2aGREo}{frI0(W=o9QUVhjr`t8UP=@ZX+=UaQSw6%dSN}!w`3bbF;@Tp z@h`Y322}+C9zURS1g9|bxpVuuiKX+DFjb&lP`HSHZEAUFd`?|lYffTiTYVKO?~PX8 z+XKbql+=;qCca7cNBl0R%1HRijAwJgP;JkEUV2Z2IZsVy!4-#8ZVM4h{Mtta3g4UU zvPXgAU+@J81#00poD;$jXP6Od2KpWX??1lBdIY!p&pA}5|9>C8|BpX^`SQ8A;Pdi7 z3&uLF#ZV_OhP(VHj)Q=|{y@$Gxu!TQ0MqelOaq;R2M8v{K>#j)!7#Q;hC>PjodwNF z@GR&81IKVvOj3rvVj0Hvf-iM&aj?%8!Q>Y79IyiwPK?8gwXPFfC=i&BZJ$*0(Sv{% zQyg5NKLii^3=Zw`XtXCCs9#h#lGv;i9jO2GIMCzFW6yn{tAJ)kg+mBPvk(#@2h0cq zjNfG(H{D8>$#~^^13zSA1Hvgr5SkOkxrI0fp%9*Di&{>M6MlGWp&zbP6XUuWvip3^ z>sQrXYD;&nsH-b6&adCCeJHDycY132>;236>F-{(@(0I*jh^d|Y5T(t*D9J>JO^KW zd9?m|_J_jk*N+>aLr(%Sqlz{UnWd4D_u{qXj9YBEYBKvI$~W|rHucZzripVDNaWBDuGT9Q9J{ih#>su34G24~RM;NA`*0 z?fuWSc#1#aGum#D%{DK*mb0l$zM$hcm~59MerJ?_+$VZRTfzlsQki+BleyIP>9(VE zqj9$-`wxVpsaCZXjj3}$LL|*5gHZgUVElxi13~uJLXoY?K^3NeCqS16kTv1|GoYzu zKxsu%V{Gj+bG!DnHa7$RQP2vm3BX%h53RM!uI%y}&rz>-uafZTB zdyXYK!tZTe9oI*egdw~79C_lf8d^^0JTGo4`Y5#vYN3HJCGlLJa}0zv0+-`nD{p>| zFwrSDT^78~_K{_k?%NbuOgX;GvPu1=`q0Lw_eUyavr@|Ui-hNHzrnSxb4BJ%bhzf$ zsMrvewprDMICXujonHLB!4{qYu=iUe@q0M%~Xx$GPL%b+*U6NI7WJ zxO;+k(u@2}U-$M?UASqVd7)quIiSV9%5fUTJtN;_$^?{2 z{2(RGM?8g6`B$Z|NgO9Km-ey%d61o;42QzVXmWx~E1}@yY&XqkIR$<+n+>aRl-C>v z!J~y1^#z3y?~BUZ?;$c`rO`RxeL6M_-rFkoe)^;zedVdfq-`h_+2R;AxN!kVY;dly&7`Ph8I5@&f)gZl+nXvIO@g|7j@%D$*x3i>nV%A4)y6< z8c>T=wIwmL#NFag_IoL z&as!vPMP4_+TtL!xh+>l$jeD=mQ`b%YxC=<=au5BTTp8rvq*18o1Jk|aJxvFV#gD+ z?Del(#JYuu%(FML+6R;A>N=6raXBe+F2$-$A)P#WMP~ z@CBd5XbaH9)Ikh48$&! z29=i{tkk3rhL`OfK6+eddsMO#IiZ<}-knz3s zl_7~2TI`cJx<7d34M9+b%e$XiGa)iVFH#@r6f;x58+X8mR9KqwODBEh{dg%M*X-R- zrzTvkh{1`id(zfJlB{lDnAGkbq#ossMeg7|4pLoT*HUE-FaH>-3Ez{)=X^Gk!M&cgPn;FmfUol_W z(`uF!*6p8X1Q4hZ8|3AJp--t)O9Ml0wmsHyaioZ@!s_v#$>SQXLLKRRS;B(42R5P_Y4HivUBdlK7ZS&)J9KR#D|x)t50?(KSxjyl)R@AK{+GvsxxPW~h4l;rh$ z4@k|`WkpwN&R$WwV3 zz9nZJY(4y<3DWG+?MWKM)_p{{IkkLsHq83{izfQ!*t1_7_jje9{6cQVUfd&{^L}wt zE{4TPP=Z<9-`J{5eY)^Kdm!lbDlDflPEh?94tsgNvd8s3z3gUpg%|6r)Xyd_hyQvL zew{p`^Or`o{q$aC$IRRd`^LRzE<@=b+C}|UUdfry^G&L!jT-;-VTs?6SSw!K<1MXF z^yeN7cep^E!ji96md~Au)G=~ksF4aS8fBNxsNbl0QUsee3T`jW$x@macD&0mP+;yz zGOgsfqUE+pe=tf?AEOap-X(9E?;K(g!eFUB0i#!1jcC4CuxD_O*YJ6F@LMQrtuSdp zM=w!g60>c<0j@c*-p3F zhwXT{6IFgzs>71-&#V%S#@}|^565plh^Up5M~=;oibyOx45(FA5!d?c9Q!V(_sZ9{ zym%cRrbw?9k+aq^2^Fv(A+|eb1iEb(Z-;NN!zc;o<;D%Z8(gI=8PTJhmqR!M44qVM zq7=_!l{^V&40A}N6Q+?>$eMNc(Q*DjwVLkX+H9x8NR~T4SX^Yo9y8o6>ne9*C_GRu zbj--Jh<<1C(DHe~+q6shy63&;uc-c(N+%Sr*UcBAaK8JRe*b&RjLeGQ*r)QYxHsm- zoQ_B}>0=0krQ&R%NPHyAnuF}eTW>^@ohmDET}w5SY?(}l+Q0d6A^SAKH6MpxWtx6Y zHdQDdVk}`?Qc&Mb5w|iEH(Yne;o~cnvG{k){SjLcpXI+bD&%t(zT_-?rCmk;l=k{d zNmtFLutw{_bJ5-D1)ScRf?ZBj)h`;je#(41L>vatvD4X;#?o;aoz9p4ffa(_zi1%)+17|(`l08uV4I_+99RSIWL)W9-JYl?(n^1)$(DZ3^;?C zDKnBRmN+Cm*dkTxh&5jQL&h9q-eTl(7@PEqRZj6lSN3|g%$;*cE()C&?EA^E=*0n) zn4HdohjB3OMfS>)fQ#Y#Pe~ToWA|CaD6SMzC?qEB8)$Qt>QQ!Z7Ka}1NaT){ly_iAMTRpD#3^-eccC(4L?Sr#lr$Ghb$0qF2Qnql0T0q znd!_LEM!ARo;sdAq}-#mQ73SS5Y8-C`<153FOoK9fRcwWN2nSnf2GVUA4%DkkbYk@ zV#H;mDXfWm`_knxHrS;Jk0|{4eap0f580dfGKymlr87x}?;_Rim$c4NMNB`D?h9wQ zr7-aR@aM1iNbeMTQ-j6%4|ytf%s7R7giCZOTjH@*bg7+@I$zG%UZd%C=U&o@HG~y< zy&>ufx_62G{8ydcF##>fbJLOiS8=plX?kC9FY(7#!Be>+jSY=c3$LBa((R>*9ffH< z&^M(^rM`A+~_s#pj`?HB2K~Zj->xrLK{i1^|Rg64X(Txkz6&l0a zK>(wdec+9~94o~E(8ey}9qDXUS2vl)p1u|PhJ={~mjkKw9P5qUoeS#)-uo_oK(li5 zC)W}G-}sF~D##tip!A?Vq2cxYdk1pOu)zhSbtFpflHyOnPhPFXUCMjy!z%&(a)gtX z8%-S_10VX6_|>`1YW6pVy;@h{q5U}UD72w-c*D0p@YcZN@K>}sPh*SCJ!OP{s4TB7 z$j7pL6em9x@gwJGpK{G3$Ts9!Xi`%+c(_W#x{uW9%r-=J9&a0`nDTl@vZ%OoT(Wj9 zYGoLM1q3~;dyg{bYH8$!rI-3Ua8-E}+KQOOe0y;9P$&$H`@arR9E9UnuT`pDzm+2# zgOJA~0xxc$Jp3gT&`G8-EL4k?7Y$=Swa;K@p>`_OKv|NB!{L}-lI}Kl@520&?U1R$;>kH zBBxggdJ8Y8<~HBNBYR-za*uZE=>xx9e8x~DEK8SfL|1Q$BaJq$_-vg36hHojq3 zMS(CV>|q}%XaIkSLRKr0`^~d==t0>KDKRhGFETb5qIRXso7aPaDnldRmm+?#M?Cs# zPq%z#D>6tsX(Y2`aDE=7heA;93^LgC9ZMZggSy@2JRhtXlitEUPq zt0?6|9DU?ZRvb;fIW#;1VI4Rt4mwMQoSV#`fsvg&>p2n)4#LCYq8HhvzE#WcJ_v>s#m97x#qu+X0*AF9mfB^NI@8(J!^#2Ojw{D}K^%pR`n3Xd@+ z_Zx?SgkC?mRfN3co8nSL!u3HwYz za>fzCl4)7S2cAT>jNhb(QlS~EMHz#=(>m)R0YBPw)rYL)`wIEa5Rco1)j%4@(6yMUC~Xp0Og~RgOMp}Kr~WDz`}BMEJyj}is(vNelHTg6dU=MokLC?>Rdlwt zT{f {Fw(ucZ2nWc{n7vT|6Bvb+lLbY6?Te(y#fuV>#`^RST{j=!md_etJujNOH@ z8}`BLXu9_;&P$TPb`Xfr#ibw9=g1{o=L zvpn7RzU!i1qqkhgu@c*+q z2NdZR6bPQ5fQA>B;~WReZ>g~`Hi>auOXgBhbaot+x6ckd-jfn@ZThupaz#j#Oq%F=gf&|N<7W7aA*`jL|N zolp0j?NZK*ec3}?8vPts_?j?*ZTNM}G|>CrQ*&ZOY%BXwb#eS{ zQD;4_U0<=46&(_iH@V(I5}l)=D(Xfs9+K}Yj2GP+@Ylq~+s*$#KPz*LKJX+q$035hV|mz#`jve;gML<7#PcIP~zYx{YOTP27C= ztMl#jW5rb#(S_gQo;)6AG8&AHh5Sg>q-3$+V_z{7;aoA?((t?W{)hA1;bP**nR5y5 zqaq%~a0bfJkSLU;3zdWJV(5)cY|<}$!JR({h=>=Pb; zU_yh&;5#Po{?xm+@wdtgY}+&UGTJ^N^>dVP$5z+H*FNP)G|w59<_@|08y}3gnhQ=- zIy`;hZ!`Z%Qz%a{{ngD(CZUeB=f=yfB1Kp3lU{tOYs*-+_;LN>0mZAfk}bPiHX=9s zUl-lk)haY$N2Wbv3vU%0B;~QeXEn0=d ze^Uq%+-JQpq=Z}iWO6f^Er(pd5dv3oC?ve4C$Mh z5!Ul%5fhuv#Er{i8;rlxuX*2E)8oH*EoAR_Ce11nUZbV2Vbnaf;r}}~wcKfkV8_z_ zHR;s%7IsPtWVpT%MbR_)AYTisVvh{9bepuhQ)cgJ>sAz4vn{LlqO}49HYeo7-^$4i zcsd0_u{G3W#YVMqKzP#tU|SB zCUXyM)6c0l%|C~B)m&<9+3Q#>dNx)#@Qx4Ksxv;k;)@)v?N&E3ws5+sP+dCXbkRN1 zX^7FZXGOPgO#<(>d7hw(72<2(vxUj?SK7D2bbTY&B9g~fV_6G^#>Aapav1cLr(Kn5 zYcIFd3Urp!WSH=2Fe#8Tm{6R)DVHkojDDhd!)HaDnmVm_Hl6O<)y0_f54obgZr{@*Q7- z2IaqLp1JcFCu-1ET!&`Zhdx9b-^_5kcj*cb44A zV8QB|G7X#V_>n#NX%SC%^61F~9@COHK*K*$_2@It%d)HV={sJK=J$j44RZq{;;-_9 zKSRfN;ft-ZX59Wz*PK!r<~NHGH@{RIdeXn>`7S$7Tbav4McQe${r-Kv_zblRad(>R zg~s49_d;Y^{gfa3rUw6$?sz%g=qyh1K>93ASGLF9W}{`R0!`dl~CcSL`Ce%tHPz61rbld1ne@V4z^)QdrxYfEy^wyuxg zi%V;KAeCKcoUkj^S1;S!E8FMvXfc7-UGhll$egb59ml@R_uO|>GhJlQNfw#nhF1;9 z+Zt~$jar3oKl3I@Nj+O|Sbb6JmH($mqTNCb)zU!8rO2P*o+M0kZ12K;&AWYReD+(d zLCx9m#&sm0;Ap#hR}C?(u+rq2MkI50R6DB_=}0fDa-2=bCR3#H@%TK){yE#(y^>!V zjf(~>V4W>4QJRhXoLLNh-01PI(rb!ezOk}TIItJkXep_+)$q;85KQh7G%$78?kIUeR0kv*@WJciQTW|_GC`+o98JpXVTU&lfd<(Y*rKz3kO z|L&k`uP^r!gnP)fby_EK;$okMUe}Cu%UXqvm=gVzFFnhfMUu(;ZDUj4)@~OM!lx=i zpC%Lw{ct^bU66HFJ5&+8-7Njjf2D!~U*KKE-npY6Vf_`&QD9X5jJ>c1Bex8Y&Ot~H z#%96*^QC*oEI-{VmcFXrl1ZSz=jODLA?0E#P$GeJr&ERq6jwc(yY<}f=Ill2HPPfp z+-v8a)i}RSt}VPg>XpAkeqh5D@&%d z#`AWu&q_AB!eL)qh)6bXzCv7fNfmbVA=dAAykCIHT7Yi*#JeoR%+0WbI6tHc!Ld0p zvUqWw`bbyk2r@hB9+}|!^m*~UfbLkO-}wp1ts7iN*Shhjrki~>EA}OK_q~~1-)tW;!mM$8(yGw7T6^Ege*lcs#zTARY7!{`*q*<$J$%x~Hi2Jh^^J z2_4ZM7cbKGmRPUur}-V5P5x$NYBpK_BJsO9g0^9~y_!ML0je8@=*ZU$=T5UKO?J{1 zx4e6AndfflX3Q0*=%`%H$1Rdn%_cGjr-qZLHO1UD!N3X$6g4f@3 zsYTkZ&?Cf6qwzo^Y4A}_RIiWhNA0zQyn+k{Qlcf%=(lwgvnhJEo_4&bSslG=_g9fV z0j~TnA28mE3A;J}{b-|=?A76>&vI8_?vtw}lFV!02|6QhOpiRQx-?AZEzsEpv)Jlf z6e*u3OZXNY$=*14quO~y^{J749ZHgFq;RIIj+T*`f^Kua>AS;^7X5&WA1-^#IMo?Y z3jIHvy#-KQLANy;Yz7PN4#C|eI3yticXxNU1Twh04GzIQ_~35AA-HQGxC9H5H%Z9- z?tkz9s$RV+hB~u%_wLF{X6-kC=jokrJ;ds zppcTzoMFBw38i$ODlKKPhj(I)v)_hg%HyQ{P@zi072{Hqe{>&gj|*?oL7pGdU+Hvdvi})M&JR8rbSUZCVi)1_NJme zka6~#*9=6ObW98lLNrKWvyMl?8QY&vDK}Lv9pT5tU-rdWQrn3sJzEeX++ipdcXQ6P zfU<0xO8kH-l|fTq1U+@sOnG%754K5D82a;H7&q}0Ef_g%sP38M?4TLI9-b{vPR2GU zsO7ENY6_)|StczAOza#~_tnS5d`!sJoCWUADQ8~L*%lb=`nKpR3yEu+ExT!+t?@Yl zR_y3Lwa%Vf5oKSIs+2J!4^fR;rKujC&WAayrGj(#75*Z9c$@ALlS3gByPlqnRbD(J zotFj39da`cRdy@M^&?~b%FN;Vi?kXGN+$b^ZG0-u%VtX;cQ1qma}VS<=Ky8nl|aF5 z>;C3>f~|rhjuxKTI=fYpx{hG@g5lUq=P241)F$;xlQn^~VUDimf>f}qy(=dHVlf;L zVz<=V7rVkZA?*3!FFdrbHe6=V`m1U$d2jJ;+L0fPz&J$CEDirF#QVTlMW@`Uq}MD* zqwVd9F@*uB*~MzgVvuZMn6~LgvEL+n-D0b=v%k8!q3uHeHg*@YOO7%BY@YOd?7bg4OZLu=Q|31nOwtM*$CtR^u=lVX4Ob zhSte93CUl2Gl0+ult2*aoE1tsGy`ytIsn78qyl>-wxR0I{S@ZlDi<5ny zOK*O(X>6`VpKq;<+b@%V|Au3QgqSJt!t~DD3Wa8blnj4~jUgqbBV%EK{-z^#=!os; zadS^K6I7?XC18>DyzCH(4X?=hAen(HHIpwJzd?}ST%DXD@qWf^nw+smg|$kXia0H0 zB(n-VKajR2uEf+i4ulZyTNt7yJA^es9cAQFxHd4P$ZWQ=E)!&Jb+h@UI#Ch7|9lwy z=8Ns<*q9}8GUt_GNj{OkUTmHei(8L%;B}!|4~Vm-RQ4;OTiIlpe(pn;MdZb8D)1QCIfbq+i8QW z^7QKZHWIagkGkPTwt$K7By05-b;?nGS9cU>;-|nsVa?a~YOLP)XBsW16laYnYvT7j ztRQber>{W8b+T?3F zo#{AK$LDoF@3;t*-Yf^a(~TfHd@bB<*?kG(w?A39=l7d~;dPDhK`Yp~L|_R)*4Eb3 zJ}*`dp9L#T8z^%7nuR*rO%xQnsVgzz$}NUJVnCc@!b?FBFUg)|4G5C^?@DtQYmm*L zySxH!41MmL6M*vxbnN4{F4n2|wlSL500Q(6a_*J;C^!e38dvc-BVnCX~gD%4rEU?ir7G^tIco&uI`JHKP`J|I1g9-Lr6&#$LZ~81`%!vockFduZv7 z5pV*5Hqx4>cZbdiC)Of+x_yg^O>J?4;_3B$e$9+0e(GLCNw`bIC}eXFJdcZ@M> z>QrBP-N#vQmF=VxxWnrGRwN94Sfx>_Jv?R*;`I3*gZ!F%U1S`&wX2pW8+2vcYaxWW zd?IZUjQ4Rip%1R^G;-HHqaX`_ zMFlZM?NJRPOiPY}71L*}<)08j=0&sOY);=u=nD-btce#VpX^%BrFV76-q1d#D3U6G z&>SdL$7|QZAXwJxUkt(^8z6;=K^MODBVB`FwqZp1O@;3>c3G^ga3`iG3*VctPP+9D zMLEV9Thm`Mvaf3eT(7qs8e@~8uMS+OZKT{2ozSU94eWgMe&*35RrWli8%SBFBTi1` zIu$G;yNB~(f#II;)7@dcD4pwA@bM@8t40aBd&CW7W(Vu0PMv~xJ#8Q&kxxI6%UIu^jDkGA=h?tV<@yUIQ&L&|E66Ltd9IktBB}*i7`=JJxKO*voEo5?o zr3PR?TQ;g^s(*Q&TF~j8If#H~_7!V0hx;fX7Hhs@L*;=;WOU8qrUfL{V{rPQ1&2Nl zX#x7o_rePV)Q%N|R$PH-koJO>PW=`R8W#0I)9w1tLkeS6(HFVVosLM(gLH%xV^s>a zNAS;Sz_F$N7^Ghhnb0omXM=v0i^L2DoQ@kh_Cg$pK6_)>wZeK&UJwkuD5Qm{F8HY_ zY`9kX27N;f9*rXX7f?P}d>8==F7W$(NXsAu z91xN>&g>M<x(`6Kb*abEZ!?EKB#PNAv@SFE&Etj*?jz--0<;4?Rvyj!8{oL>h2@Rv9zqLxpdV> z75DKNJdg$NTYYXto<^mC|KLOMsuJIaDh3n*ql>ytl^r3jd)U=y$O@~AdKMY#-xzdcIdd3bJ@WV&iaDv@`KfgFBmwtZxW5*(FKF9#lY0 zDgAw}tmXv9w4yQWr=*gqoX=`eInuM$nv1WxsFivqMmCb0g8AdKFYa^IM|Q*T35-8c z;@wh6k(h<{EjI-Ykl2Nq+<28!MsMvZOOlL_=TIh})sA6Y-aCaFBopU$VKAZU9%CC- z$ET|k=Eikt9~C6ivQh{6cWuc|of8s2;*S#GwO(c7cI+Y1E> z<2!w&m$4sE#Xl-$EtpwmL9i+NG0QBp_FX5F4&L53lFK=u2x2#< z1awxcbW|rU(HL_@hmB2jR(r1ABN3+%f=?0diYOb08`n}&#FSa=3fXMya?ViL=@D@AiIoUJtF|s_py=TD>$Czvo@T^ z7NMTiQGT=uwLm>9N9V<~E6Je&WnE;PnL&Q`T-b8;UNn1l0d+Ln8$$7$ZZawLH^wDudejn(s^o)uC)8izt$&ou1Sl&^U4PaMzqSXa&9 zSGN}Lf^S+_Mb@v;Zy#Q;K88eq9M3rLvmMV$&{yl|uNAT`x%9k`1JHXx_`3HI5k}27 z%J{!#`#|2ONDxr>QFiEO*Ry(b5&S0EOb6Gq3iS5vW45L{nKRt+D^i=KYi4fI(}Nh@ zyD+1lsh77OD($W#NOe&n4DNH&ZPKZ4Xq@dza6UE~cz>4pE=kGLF|zqBYHADoa&9#- z*x-FrgZNL^?{r?tg{-x1pat|U6&{OkBi}DD!b|t5%7$}Ux?Vme)xYkEGK<wONP-2Io3V~|8prPSV2L)NE_PjeB^s{^7{a?effLzKABbVz^np%L zAD_86Lw!Tv*)3nhw1QB7Wm&T@nLd5YtPdxRKcXFJGCrwA^t(`+G-&jDkwd!Vq$Br~ z1lRb&Luyg~uFA!=!iZ=+$Nb{Z>G&^)%3O0b8MC?9X}K(M{a$em^lUEM$IM^56DByr z2xo}ezZtr|w)IO}F)*~ZeHqa;spxnK3hBDAqD3*rOIuWw@q|#yzl~0lAGM$7E@~=! zwAohY`am0!-aW~HSOaq0mVmcod~Rrm!WU${sLqB`KZ4F`lb_sL`c|*X$*78E zPY;te_RY0=oB0HOTyjE+t{clIL+)NpDp}^bSWD#B!+0ZQNuEokX(pxzuyoCQvMer0nti>u8yo;C&J&@)Vd5mH_?G+@ z&L=6QtlZhYT%S06id#FPySD1u(`@cdfLmE;NBESfj9Y5nur6AcDTvB|1ZDPu`rPdO z0i4xk|7qHUe=@t)qWY$GvHr|BnCc-B^k~&1GM@AS~wGz`TSpGs`C$;1(lq7MuESQl0jW(?gvmrD)G@s7Q+Jg(hKR-br3 z*60nXs{{{MJxeG}z!?!ONnIaq*x#6;{5ZEm5$H|3H=XtAP0ue*@8o&&RMS!U{gZpaCftVzA81K)*x;QlH^aXBP3YGVf=^T;)z#~@*IMUTwgk`(V`ZH%}D z!-?6*;F_z)a)e-j^@#^&E+gel;I$ON9=mT6^@6wz@5qe@igqY$)0u{#*eilLa1ypr zK~dirVFoA}!ni!N_;Qf6D_*)bZ(cIKJ#(3^yhwG=8C?%;)~wT=UuCPhxyxU)f#Yp% z^w-VLakMQj-(1eC+4Ia+YP{0UP4-M-@~^m1h%9D>1%UUAYgzSg z*3JWjx9Utk$1)@?KKI3Q#@UUu=%?n3q*<|V$V~?cS-=xS9FP7SRFx9RWtx~Ld9OA~ zWg#k~V5~U*{DQXlFuzj95d(9z6dx?OttN4v`v$OxrMRjVyJ(u|0V zzHYU#Ib|R-l#@Ptk-XR|xxs$$(f<60Ep^&HsABB4jnBo$pK;0(Q}!qa?GtX8f3n~E zW_D6>P6+b?;mrH%tSkkexADvY7CZGH1=UfrNvQIcvIi=2zt~`-4u;js#V85u5H9uN z-+rne{nTtZSTndhhN1FS%r>$i&UgIa@=AuhY9dS;PoXAEFiZ@4qj8maH&pgRZ81~l z7^tuy6XoYV37b{V<#fbXON4gPc(No+cR~2@zWh`eg=hmGK$@(w z(3}O{?g*{FU<;}wzz27+E+dxcT3aIKKic7;#9x4}2&$h0C3$qWi) zz^^@g@J{-8HRM`yU&}|a7{-`hd7E0hw7u|!L9WuK4jt9IKCU6Tc_Na1SMee9?k;bh zE~QZvttdJWDBwT!xNle+)-=D?>!*vlY0Qo<**G5&(bh!2ZcK1Bx06-4EQ7{VjRGQ< z;I{<3|NOwkIup$CiNdR6CGl<$7Q1MZ8$03*BNx^o3JhJ<+)_=3e0JFNzNI;fN)vt_ zUu4>qf4rg8L*v_laSmia*?Ak-#&v?8K9BB#@kZ9cDy$AiQvlVdyFU4pQ{E0GH_m@A zTv2>595q~4i&@6<72698_=VZ2;$e`GO7f6WA#*~Wn?Prx$u|2C#;|Lu!%+3P7`)j0 zi-iU9RXqFro3X^LZSh0RnP|tuNcR$kfnlV=40^iM8eR1i=Nh4uZ_GQiSor8Jh0bXV z=y4%f2)heQIfX*9WYLadC)^PNoHM;!RjY|#x8Va}F2eB6rb`S3kWBm-iiSaiiE-)9 zg+l&*1&w1misVT3yiuWe6sTrgSg3sUj1t<0G~d=~%;P*VcTw471$5L?YqVz-tGb2V z35UBk2G-kCfnP~jdjk@LzBO2#tU z2}og}{HbW8Co?}q-jtEvs$xRi+-wUnLvFpK#1g;i;H4N6k94-hs5AE$UlT7JoY8)K zG0wGf>m1C$7JDEE11snC=^nR4$GA35Hj;rY9zXpY%d3R-ntjLY9OSH`Px*P;tTRz# zQmjc_UvAQl-1sFnWD5;mNjMnkXue0VyDfw-EijtKQC2N>`xlXy-F=%wzA|>&`Ae@h z1`x|M)evv3H(D|V11h9zn4Lm$a8|e7)VUE!|5ZTyG0-68hCiNjjmm-N3`s=e@?~y? z>$C!HS?W2jS4)@&#qD94)w|Zf2;BSkuybR1X@gi~jN7D$TOM`;;X&;yi5@jD8LfhM z&WN}z)l4DmM)-WqvR>c}@qT*DDbm>~ETJ$+j;t3%sL&zN33N!nVRp>1({?VRvZ`?W z?lD-Ck#88-xF!CvfPdfUqe$*B+Q+P-Vfl|D&Dd|~%(#T|Wxz3KE)lHcySqot-u~GN z$FD%|S4}u})NgT4X!^IPhhG(C(+iM9WP7BH+2I6Ax3rRGt-{lp5h7xHTsE#ex5w>n z)N68Er^>)JJO_O%u`$6gvRxbZ)2*#?RNh~`AYh~&u)e)z(x#%*E)wtgn(z^(?UXW} zDAUF+PrD96zvhzdKBvHg0TAT7#rm-Cy==rCnCs4OpgRw~c=m!QOzFMh#-Qr8(8#Q! ztNkXuvGHejB9*a31}Y^l^^8O~q)Z$MUI8xaRENM*mJU!T*4Q#ZZkP>M(D&QTcE2I8 z4nM9Wy)#y~vI`yMCT#xAmKAblG4;LWF1^hK9qy(3*_6C{S%Uq$M|Z2jtdtV2dP~!v z+E}pRQ5K51@A56}3h7e`(j{o$6t;42Bk}Tuq|sZl$?@b%C1e_?7c-~D9NfVB)s(3Q zFbq%5u#|uVhQE4?n@@iwu(n=jFs1Bxuk&SVep9r#s)n;*9_<~622ISEoug&2S;mz@ zO!CE8%)J8?#)v{{6QPjB%Q|auC8=V6p35A?@@XcH z=Ti?6h*-~~h0z14dzzgINj~5EVQ(hsnMDh$H{C)EPB`A&Tf9>;Al^eJm2eV7a{lYF z>b-63Gr^VjOh=KX?MB`#cxX6;R)LS5A7<n{!~l5gW~ z%Okjq6KEH|yhC@CfA2{>MS#q!rJ9Yo6t~eO1M+nI)vJ8QcFEJ#-+*zKD*i)BEr#u; z+#fTHTQ16Oo4P4(KBTofr9BH}Dy8fve<1{z+1Bn3)^=QYG3Jup9iNwJmU@ZwcJckCYahbm5DL5u0)qbt(@0xo7;^SQ%y%% zdxLvt{6@HfV}sPu{VrL_P>bf%&V$v!7f?}KYRYOR6uzRUJ3u6}h^>yfG;_TGADnod zM+i5-I={E7&k0fB$2TX6|EZW*VeY&+Tsn=Vy^3XBu)ah*tS-$)t)!|aCR@@f*mC@} z2t^ED60c%p?q+$ioHXgqWI&8j2BYX#Kx%Y0!rT85I=)Az|3h7QX?1Z z@Qc3o{`uUw^+dd*EBgA`Loom9bv|fKKl=L9kENxvcJv2il9RhBhmJxVtGy)BPy33dwM`L)m6Uv&@_T*(h1zZ^t>hcKc3^ax3A%OvEH7BWJXU7dd0r zvwpfVDcQ9gEa(gAJcCzPWD$*i@6r`gz#raO*k>!&s2-d*lhNad8jT?Cau@{MW^00z%iB*M!ofHf4y!F~1w>sY zq05O;htWhMvkaA#${sKpsy5nO>{7)pc2XY0s^1GsgO zVd3HKK4*>Uz`^wpGptSM@*f z{vIRv+Y#yDzON4%J?S$9uRHfHcBCDxKELvK)_Da9u>;?Si(rpVn4PRv%$;Tuhizh* z@dWJ$7ItG4oOp~F)ICy=BF^bHvB~!@Yj|#APEGA&oma^M_ zz9CGPCc|wo4rG`v({4Gy)@eH-bQQIt+eyU<>7OfoTts%gO*4 zM^@B=)ixtxoSvZGl8AN1MuNgFn*4Au7CDvUEr^3ELC zZ#Gafq!sP_KYGH0)cwKc#13-};tXTC`sA0CWBrWXIRqlz*5#k7L%NyxfxGdEvH{@J zw?v=bbl*gtdO=NCGxPt)PnkB!`uN zEvK2^K>}YEN3j-xmW7ppCQnVCHzx(5jw**H(zn1kJ&YM$j7;dQ?1#{inI5zNMFNd- z37zWJDs-|KaS48Ozy>E(WWJLKr-mC^fLMFP39S|ea#jQ?butqo9ZDk^+A^#_gk8a) zV9Jl2MwA=S+NwtvCG5-fiPj!XaWn`{K6asf6r~2XWTh_CSbCv(3!3vgRiBsWnMxUa zUnhmW+Kc4!f1aAuwc;CR97{j4%(COtz};07Oe>oSCvCkUk>Z-!b-swx%nI7c+HCc5 zw`eHkjMeJ%st@bGRexc%&znZE?uCQr+S%KOQG^*Y|RkA~x znZM8qhXhZf7&4`n6b*hAmh}J0eA{AIMUX{T_KS+}%UV~-=?CQj(axk(ziV7Mcs(tV9YYGrX)57wG>*AdBBx9f}p=vFb3kw^cq^6M+Tqocy&~_S`vSt<= z(1ta2Sx@86+``S%^%-aasONuI9m#)Y_T88>3Di3y&)<&izw;hx>DKIM-J(co4(9TJ zx-by>W`Mqb{f_Ctvvx|(k0>8Huk-yy6%j99+;(ISraht(8wqN4lR64=9EiM{kA%&V zL}JS+oac%`tR;hOjF*-*_~3}ki#7aBg1C~luu7tXKw}SoE9?AqjhS3ssqZd_M1#`Q zK03&li%WsS`kAGmAv&l2fPF|rs-9?^B%U{A?`D_fpmZ2%#(T7% zM|_>R&?q3Gb1};5Whxh$#~L!-*(D2xKFO(}*nqsImVKkB-p+lZ?%DPJ zsiVG9=U(8&a8$hSBn-Y`)0(iM5A9}!hhxQZ%uZp;oy6R{6||zK3)G!&D3AaZV?2^%4DA~=rTPQ1Ejtd-AgP(@B642-BcCpyp1;$ zH{R{%2xqpVkw=@621mQR!g1*JR)2BMFW?!ATcX*KCS1?x5nhDa! zV6*PdNn`kE?AM;LDPdcE&KVSAU16KFqg!!zhl)<$-lDO_o@_`ok>XngoY7Zc;M){5z7iyenJaZlZNNIHi>T6H z@})d7*R5QMdcG*xsC>{pv$!>%|A6#j@n9Q%zIqwu!3nbbuxU zmWo-3@vMtBiAue4UJH^bsQO)KffnN$@f4_vTm#%OvPL!_^jIF73_>VfTERui=?3jU zgDor1mnqsj84(uisodnZ@hN$^X-CDy<^qhBPOX&UbNWiGG6A391zO6PcWTtTd`WxB zoV8YP?(bB8$Zq`5dYKi)*lVe{BcKZFHQNIUzQx7GT{1$xl+9mEJW?#V2a(c595hHq zMem{=uE`0+(0MW9%4XxEDD*~co5^9tTT%%%)qppU$g&uOj5s=mHc)uLY_iG*9u~~+ z7IJbE30ZfxlV2CbMH&Zx;|oTg$~vdeb9>r2 zn}J&T7@_psVwbmCb8$6UGW+kzO15wLiP$=kJduJLz?({*2xL(j@xFSm-&Kh-^nJ+( ze27=h_?(!VvdWr+dtWCHQ>igIONC0WcB+VWhpZpkK~>O5+_13l-38=`uAlF$9G~p@ zmNY74dQww0n5DWnQn}}qDsS`_D{j(}Z+)`ZFW4>DRsyv}PYH3p*H&6iPEo`AG}Tpd zLXy^!rNIXzluMU{61StRQ}2o5x_NX;unO~e zEAte0HR$1#%$ehQ)6tnOsFAQ}6OP&pJs#dIepw`3{E%wkg5L}TP*_jBq0S1>NkaV` zVC!rIj0*&IDKM-#I!l8S9@0Yc5{<4WO1-G>Y{wM3m^r^{#BYnd;T*yG9yBK%{23ZD z$SCOs&-Iyk>WvtxjB?J$Q#%E7&ch7JV*x(E%D6mB!dS!L8Yi|Te&pg#Ur|i%ztJbf zw#*Qh!KAOM;{Oa<`Q}<*y82}aA*Nf;_6?ji{<0m-h90~K67@;#%CIie`l8QU+m4Q9 zMUs2DCP<*%{L#_M33~*0UoKqY)MN77E7a|+T6XyC7gKftRyRVb8z`^Z@r_{G^WU~+ zP?YAX9ugUsfX}x+dXmnd5+J-deQ*33?bearUX@r$G6&WHB<4=pW5m50m>vofC?{m5 zL!hvL*N-T}^I8)N)h1+#$SWe#Bxb8(OUaNQsi+~%e&&ofGas=TiFkCO!t%Y;$R{fU zEN-~s?+bxNRj)euP-}MQD?W@Rp;9yCqNWWwssz19{>q@@BPV4d$U|)7SrGrJGfUAIK&iGJXRf! zCT>%G$@b=8#=m{23=mw&%aVbm8CYp!DQW4zo_fT~2MGvM_=s@jkDriCQMuEw&BKgF zggv8@mR<5-mM2(f@#97|$b-y%*>9D8bH%0@;h==`i?_VXW5fAgkkB7}UHl%SVf|NU z!VP-(o@g-X4HE1}dBC*{GS*{M?Wqmw7T@;Ij=teD!B)Hux}!Tk*`t^n-bYyjhaxu6 zC${FkXQ1;P6NGSN%H9o$@OPiWcBo`Bm|UHL*n8c&+3fKOE%605?c8$Zm@-;RG0@X@h} zpe(XAkSBLB4dEC&;PhiX+jagR)j)Ns?Hf_=S52U2nLHxq9>wNMq@`N!QsG5ODC`sz z$4chQWrw3**AHlaB_TE@tw;!th?J&3bNr!&x4eHuyR;Yil?C1LP`;UCRWG$hhfbFJQ}&$C-awHC`qDtPsLs-1)w@jnoi@{hX{Rw4nhJi5h|6jGA*w!w z+-akj162B}IG;*2+as5Afdn6RoUg@3txQayH}S4`ak_Xt`nm9hp~@*NZ@RFQ08D); zH)QAfijvOilJq0NK2?&WZOneU$m-u1V<3$3k!sZ@c%{On(QkO-Z+bZ*U>Zr)&r?I! zLHG`}N33Z z^a3j)W-BxVD4vyq*z*b@G5ibn_b%j2hFJ+GU%I{+={H*@R~%mumvf_~gOwV`_VdIU z80cD#nCJYyY*mW^^grStUBk4^~7Fub}f^XMK$Q(Zgs?I=cH z%Z-Qs@p*R3Zsscb@|^GT+}_`6pFocU0Marhy6KZ8ppN!c$|dR#BPzylS$E{XkH(Pd zCgDKG3tep7zT%}f!jq=J#J?ZMI-)-k+vGi2|E!Qd=rEtEJJQ9(QD0r5p*Dx-vw24* zc;>JuaQq?jwEYO{@%Sy_2iECTA309pXgJE`_-EwkFJ58vhnhL&7YCT-lBtJcl!LH_ zH*_bT;fLa$>0NWM8>G%L^T#{OG41LA?v(P|)U39D^4HG}l+=r~AAD>N52Y#Tv-W1p zT8ryH$@!ri)KM1ds-V(o6?Uz@#7J6&AfhR|d1u5U{YC1>UG^x_MPl(Jvi_ce^9}mt zGKz@%sy0Rx^^#mH3l;U+sA--&#Ry2pu-Gz+91cyONP8lg`Yle z8olKY?{amS>LuL6Pc|t(B$L+QE57=S%W-0GsDo3flV z`tI@9uP~Xa$cI1hZJOo|GQ5gY38O0N%Gz{Ip&s*1-IPtC8S}Sx;++n4F^tm$N`<-L zCPh;&(1^DG+Z)alhO2?e#EZdH3DwoAsJBKt z9C9+PF4L&ro8fDn;XTf{BNSn5kA!>gG=wg~DZD!gwhtVoqf%Y<08XYr%jFwhdLUx~m z)(tD6IaE3ZD)Hd>ZeVRPI7*G?t->LF`U{yFp^^+m1Ksbj7G=&eqJE*=@wrE*JF!Mf zI`LLx@LlAAN+9UqYv6$c(wqh2oCVUHUEsk*;6Vhd?yms4%g#%t`7 zaj<#^zMP2d4Bt8Pc0 z5GUc>@aY|Q{jow& zN&hqQU?A{-F7Tiz@IVx4ZX9ur4RH?2B>2mH<0p#yb!XBwx60)o``8x<~f*kr!?z&^iS>J|sInL_0oYIX?I~K7=_wq&lu_^r|7YauRA# z1|~{GzaeUR=V@?^*j00er|jRuZN8Iwp+@YUEaLw zRf%L0caHi`-NyaKdZ0lz&Kn}=S@Lc$tVVWFS&SIpZyPw3#L&N&6E<7c9xf&%bI*PH z>&Z9LBpbhBRX!li33aG&J;BpfpYDtzMpf38K7*K>Nhr1LbyrvX%T*AcOVjP$(e_rwD3K zCH{eu{jY3x7YLhApzVJEIH8J?zX|^jpD~iO0Hc4a_vd^)6bBRmTmb_>0c(AK0tkgb z@zOD$fH;5G>bIH%Ji&m11ldIZls+BmgC^nwvofNPLH2=wpHEPqzuC(FLVXHh0+7I@ zsDLM^&p$Z-e+~!!RzRFl0c-!Q-M~;G0VEY2;PM}c+rP~WfolFSUHk#36(ay}{dND( z8?+NFC|Hns0>H#y6#PFxrO+h!;A3Ven7|YT;Owa)I&r`8@t-v45M)|_<39%mpj}!; z0j**eJz)N??Mw!M#^0+7SHwSfvdGZ70?Ark!arnNwO-pWAu#=`l45ytUT~nhTg;_e792QA0p~0L0MXT}7{uHw?;dM56wt5Nz zv@IRgtke(aJr8;D!-Dq#DHvC}i9H!{C|(>MV6s)9A{}ik zfT&SpxO1ZDcC1Dq90wC@IgB5;xI}ebTDN_Xs8QNmTmYLxfjex0hE;TyILtW~k^BUr= zKX*No$JDoOQi>%>QZwhFMkbGpRu19U&@HM&oU@BFfD_MVU(9M0mt(iJ-04z9h9TX2 zM*c`-uhlSV<{mEs@W+Ig@aszsa#5^eVJa~Gc~B;t$>qk{Re5AQ@a@;q%g8c%(6k?l zefhVGs0)r4{>%?to(^L%g0E8WZ{uch_$%|@@~B`vQzmEMeC_MjOj6+-&}=zvSER3uBEF*RRg(KQtBr$1<7vWWEu& zYGm(!tvX2$5*AY(j+A@Qy|rNyE&??r>fp2q3wj1!=UcX6F6&3@myL%UF}1PL+K$=U z|3>{@V+QE|Z{*?UxDt{XxRI!+a*adE5#d}H7q(HCs6YNc zA`hE?!`pU*`NQ)wJ$wGc1`09k0R8-j=jVTL6M@@U05pH^qHipKCq(PBUH~D4pB0e* z7Z?Px&JB3+bO{s$#QuX2&Vr_@69Jq*-Mt%O077sxI|?RbS`zS&mN7ORnn?By;05NN zLLe?0fWW8u18S8Jf};gkc$#6)OaW_u6HS`|I-e4Sg+OtM?Kx3UAzc=LUh==5LWM1V zub_EI!S|d{x(t_4z!N$;1nP+W6RX7U>=4I90LkB}2Lg46>96~cjw}Ex?cegC^{qnv zds#3EFn^k%(U$=4{+0q=W90u{{Xs(!QvhOuJ71z8fD?I8a6lDm3-Vj+IBn)kJtAV? zyW3HRdm<4&w^FG2Y7bF`dwK-G0q%0DS@m91Jn zqpyzt(}i)3yBCP9_?6<(jFeR8kn1d6g?zrVc7Em2UIycGl88g!82gqb&vOk)5f`(4 zY>!FNrfn^n6?|+SDtH%i)$et5biwv!j76G=ORs9%Xwg7zM&qigWMOw=YDYp52x>Wv zM6ty2H#L+^_$t+ry%uqY$uZWgELNVPTj`eQRJ2A~^t~*acF4)~?{3!3?~`Afgchss zPPLq#p?Q@#%Lh>`ex00qNxV7W$8uH;aJWMgHexAlkIlqBJpS=ZC=TD>@YluFNhqQ@ zETer%_2w|>8@Agt%DrHKEH#J!vo7Cezzk@#8^fOxy5R8Va#+mfo%XD!^gGpN`AP?< z5EYHS2Ye*TnBljWy@;J_G^~2|I*KO1Omn)?)R9n~yOKyZz%2^OqPmv8@A0f*_0OI3 znIjN?3Fg@|*8hbb4!yO&E?e+K;6G#H^a2167Fz|-{T(b&>L}=P_It4WQ=owLc~MBg zeXD>ccy;KK%ldz6k-%%bP$+feb-=f$A$JPk{B0Bk{FV=e2rRP!xc{s8!*2V(%_D*P z`A~4dg`5A%_{;V3G-lBu(^~-3r&|~UXt)PJ1iSD23kUuLHvh2?)nbD0`Jpac z>;U%vxB%Js29W-13&Oewc>QGde>eushym7li9!gT`VLtClMx#2Jhcq;Ho^tp?mx-F zQ2Mz4)?^U$LjcZG1OTOi{Er6tzp;Nnec%MMUqW5~DTsm(o;U{d{%s#9qQQT1pn!?a z0MEgzCxBm1uAc%{o=pEGkAXG=1lmu?PnGyX82hjGDB$C306KU*5(tFIT>=oE(rlap z1Ryb20IH|&|7GZRm!d$Rd77V!_=A=HA8W{>Q2h4up8&{TPho&SPB65tP%1nzXsG1) z0GRyeTInKsvC|S$AiO)=&1g#|<^2_7V<)Iu%uJ7;f z?eFdF^WT^!SQz!yW4?>dD>Ees(pByY)Q!wLK{wb^n3Q|j8Kll6Z-;1U)jv74XdI|0 z&&Z0cjM2OuHihDPZ5J*=EveD>LW&slLi@-2Hu>2(vg?ohtrDU|2_H055;WhMgyjnn z!B-iYdb{V+S7*&EtaOX?_E)_m{GbcFMNbOPuMue>y02375jn4o#4xnMSiZQ;tc1ZL z*jkDIr4y|=2Lt>Y(r2ACU%cM0!2~<;V{*yWtphg4Js<>L7`}cR*jm2*-@`2n1tLU6m5tRg?pb)RkGjMNZP zL6|ep*E-RJ(I8OwK&iMb|LiZ>4C0!l>yafW>nnH7VTd#Bx~N#LOhbWX)@+^;1f01j zs$NLIh<&$+gfBA!Qlqlyr@8CJh4;{ zN}XksX0c@XIVSCb3`8qWgFLsP7&O?JF!Nk^?!o6*9N=CzgP$E!IskLj&=^EH$Y zdILxPDj#=6G%Bd8_%!QBiKO-OT4Jy#|f5%*-|36|b zd@wC89ljY};=G6oyhc;R2~39vHxozmd+L$rdUj z0}JB-yZ>f_GB^H7H}y9cl+fglRnS`$WH2+{zjA-ip#S6);1oPy>wj|plOsc-@qyt_ zA>i+6?wmsICDIByj%1_mYiM5$7@}LMpZi7cQ6Y!m$#>Q<$oYskxpH28Zr` z+x;J$&YqwA@_utfdj8+L3(Rv34<(6I0OEm>DgU+1A95C`3`+Jai-H7}S^c+Y{#yc` z6ppgc>SoD6i8_`jfqhT2=3geXf0>hIQE(tvFM$6Tw*Rf3|FJ*{L7)Xb5%vDp3i(G( z4bfx*{v+%8H{t)y2w`Ud20V2OlqLAjO87@Z2;P%}#>?28z{e+lpvxF5n1Tg}0a4)w z@;z~QK$jUV@Tx4d2cd=#!Q=AKJevZ*wI`GR0%1Hbs{+)2R8ip7-w+!NT`jmE0#ZQL zryav@Lr(?$rPfq{j>P{&8NUhsAkdcVeA-n&7o|VW0Kum^z~_)RDnO^F2QAPDCh(}f2u|XCRc`rCh}%LHPFz|h`@)d)!n;8O=BJLILR?P@b31$7-&YY`Jw=pqqoUe!hc_!=I1c`$cgkvLWY3;lK~-q zv^Lnhw`!eY&9La5+xRm6qkV)(Cf;$_-p<_tZu|@zi6QqQ^c$4u)@SSm?f%L(2j{$D_@hC-kl3!dtfw&IuhV0-ESr5CBHqZmC{ZnAO(nwUDE(6NSs3mDBaTC-6_%~-Q6H5C5Mnk8V=nh-3I}s8)+#4L0TF?8tws) z@AH1o_uW74^(Xw;GkeXA@RwS+AT0n$&ZqV*QNyRGlhcp%LJxDw=cC{ z6Ky%%aCCK2Q*d}2>MIC7WH>LpoGZ#jrd-+0>Fb}h^^}sO{#vpc2Fgfow5I>!XwjsC z7SD8U`rL>%7X2}H1zSaDHB)Kf)c&xw0{2y7ic)-O36|CgT+h_`0#@@L))!N2y+{5$ zY16KNW6ehBff%Ll!_Ph4dNi3^Bx`U&3^+{_$+xQ=Gy2C@0?B9Iqm=ztcS9%HKv?r6 z6%VO!?NKr2_^8GvN(E{)ehT09+b%#%h>KA!u~ul-q*RA9(tIiH3w*eOr1rcWK-MX$Nd=E0R|nz6|A z8R@?~#IdH#5?ST^B)$l+mAu*2&+(rqG9hx7=oj`_L;EN*Ifut(AHJisdU!%ez*)IJ z>FH-%+4w%qYs4G@D|!4;Tht4#0YHvU=QED1ZUUh4fG439K^mTtRg*F07YnvNk@zFD ztfV92EbtNhh^(h7s8`-o=Y_B@nw^yMHiF2kUAivAT-Nvr!sgC4k?N<;Os!r<&m<$m ze7`kC{L-XS$fuvOa#_@??yb*P6bYPG9Dth^twE@--f%4S_`LGvSn5OO5b2gdJC)X~ zjEZrNHtFNGQ0%4U>yo^q?^?Q>MgN#yfp&#!ls>2VN;`Mq8_0@7q)$2|`iH&}I2*@4 zQb!^aQkSK#8iKb&^Yjb%8KWTssvsH(d)+s@uEw4>CO@-fzeZD)7+7}^8kG*t#LkRf zUQv?dUhGh`(T~$my&2&FTv!U|Dj01>6Nfn(b;Wt0NYE%^7fnhZZ~RDKkMnR|jvEzm zNbG0Kw{a%jwE}ImvfRWiQ=THqQ1ng~+%O|;|jj)FJKWl6KL}r^p?J_TJ z9dg?-%g*gMT4rQPq=Q8g`gty*V1@Db+k`g9G@7c8uUe1A%Nw!Z=&~m&4?yGM*m(lz=x zH?c+zPb64PT}e!(Ny~pV0VJY0_EtjQXUQMPZ(I@?~hGy}!mSkPWm--E^%8R{4RUA9%$W-?FueT7s*& z;3GUZ;6n|%FqB<#dj3JwE@p2QE)2^E9FAo)NBzQg?`ix}MCQm~TLQ_Mz}g*dWv??+ zs?-P6#ei%~v}?e&5JkWc-M76;XF-(p)x)t^1g;M$?{<5or{2CVR4*aUXs<0@U)Am) zd7ocuOZ~pD#+JB@Kk{jZDXIzbN(t4i26g~pXH7sBhzlzQbH$0;O{PY^dR}RkXAH8$ zvOsf=zJ88w!fV6HZrefnLo1xLc2T};L21IJ=~k-j9S3w>eL2yIvZ?Unn&$8&u&Es~3liB8gQ@8hIN1)FhJi6T!is2n zo>ArG2KC<5OHv5v@kV;|$PDv;nR?I@SRYga6F5f=s(`uekzHX1#(y-)y^eVTIdDWy zfhjNOM(RPwAVNNSAdkbGNF78_w{&#?GT$HT=dZd%1XqKhn=0x+WM+_>7#vo$IC%h4 zbp?(!>bhx7fLf=4gP?3L8`p4mDiGJ{R5#oAq(gbRj-^F=wn1Oyia`JKIkP^Cd-gyq zk*q8o2LCX>)d=WrWa(;`|N8cTwGAG>gO{}4Z2mpZFd*f$J$1WvyGL>OXoGoa;g}-I zYl#Wa#B4nNUQqj3ObXPE%9`x;CismOV)@6KXmkwU*)>; zm=@M-6RMO&jH>*8g3Zi*3P?ttdh?wIhTjzQ6s(mNFt$2j zG!HWe^KiPOW!5Ih&3pE^$-5|MdW1*+&UUBlvj0wRpK?&VKepMYu1PVltXSri@m@AC zJ6U1C@Od9W#@mHzkmM03*Ltw?NTU0|R8ud3+I!&?uv#(^Vywz!?pVEPw~MZ#^d)C= z<+k*AWJ+@$P5Gk0nvZ2)BiU(9nX5|C%=ivEatLNm*rGdOZf z3haL-MO95LvGRq7G`35Ms)kY`xR>o>!Nj6T-x)r1&wuzZ%l}LopY7rWG?nKlAS^U> zpR_9}LYk8QjitHhQ)n&Ox$Z``&Yz3J0iGhTy^@O;8o`fv!oJ#4xGwV826 zmDCBf`=b{%ETA*GkvN054D+6q{4) zUS7(RahS++;I4%hxs%(}$TtWmh8}E2qMkRW2{H7;fqE;3eCShYL56*qTG*v@BI{E& z$~)A=t|*Xs2hY8WD3cc{s$FqNBsDjbu?W(UE=5L9Z~L$=n=IkVna+)m9V z?R&3Z(G|)>8~L&Hfw9*`j^idHX>5LJG<~vm-k>zr_?7&$Ta%uw^3s<~k926gqlUWr zT4_vCtI30{6#;qi!PiDSGI#gQSQ%>dI2vP-y~aH7Hk5_rHoP0=>VZ8H8j z0e8paDS_%__21gA2JgOR7SmbcNTV5#skN$Ts?z0d(e`ASVN2rI;K{FF zM>Cml=k$$n^gd*}Mmo4|R}Uk4EV?o*GD4cO6hH>Ly$br~zHw2KErdxv4TqMHWN7E4 zL}*uEE)Z4;wtUgg(iUQsVwj(e7Y$m*dVS>d)U3%(qCUhb2mM(uAlIOvSe{m$PFMGKSfk!z2Z*IDZeGX zx36|H7u;?j(sa&{SYY^}#IXF-wBfq#4ErQp)1^K1+%xYKbiazfdX(fZhWO|a>Hk{A zA-SQ*c(AR(gA)z94ikehG@!={!$%^=!!{NV&bkMTs|M6VwGoA!{;%x>Y_a~Xfr<~A zjzyM*q3?IN5V1sLe%STH_U(aClO`%5WDJZf4%-hscv1f{M+UQLp`t*HGLgApD&+zF zut|*6LdAouWFr&78vb?t;X*hIkzc~}%s<;F=2~QY5U~c%Ev+KQw&f+8;zU7Bd_Uu2 zy@Ab#U>bCXgnIk_{4Yi5%+eY5G~4y*nR~nT);Q9t;)u)|VyGx|)*Z+yHj_ot-H_u~ zzT|`QM>l?=m>sTGmR8XJ39_}{XZ$W)&P489M?j+)KKpOalU6a{wR@lY<1{(ajPxLT z`m*_h2xj{TXTYYSBHa-f$R%EncUsu}(+{zxujv?{RR7p`k`2_&>-x%PP90e5+9>#*(C0j1Jc3WlklC3;mIotR_1_ zUAY3HDPA)#6(!JEM|j+T%|;#4$-EloXip>Cqv)bPoyLE1SyS@E&geuJDnnl=B-BMCfSQ6Cg5pHwD zxEDO{>R%RNP3*>=Cwm)VW6)A5PGfUpkC-NdbPS(z>Jd*X;KfLOt9Q3`nrR?>m9%2$ zmD0a4Z*#@nW1U$1!|>HBt2Zxxm{L4Pe{G#zDn}C-RDtf*`C}}OwE?Zuwl%DVuVj#P zhm=@BZY^Zx!%ve(e6u>)WOPsP)@A8~(h&_83id{uqH*3aL5@Gl_yyumQfBaSJu$dY zX9j%>s7);yBz%@2%P%4&NmNu2{&As0*-pei<6CB-gJp}3v86cmT&-jjhAh|gcHdKv zNm-McR)eUb3SGJ+&L|v<2s8dZhxW+H&V{NR4 zKo0O-X$2AHL^cCEa-ql>b2XJ%=xN{KLqlXsWCpT?v3TQE2Ln43v8kDmdZpb6I&vpc zyZ03fp3Myj4G%g&&w%OFb<-+N8KXY-Od%G{E0`*&H?$-icHQWcPZfdJT*ZQ8bSAYJ zAV8G0<|-^5me)<}UfTD}X0V&k)xbI90L9rSQ7rqG|2Qh~hkokVpH{nubPry4ymwU) z{&OTPqA$jGyu-)WDq_6eogDI`MH`N<)Gv1hMFW5@>^fDTq4GYxi$g??${o58icG70d8@e%I zoe6yA(Q`Mr>I8oq4LUq8_6M=g-@U(!-2`|7?{E$|Ze?UtvwUB*L$|wK{0X1?owZlY zzRceih;)5+gx66xt=7}j`E5050;;(yV~aGix`_O=_GI@ukH5tl;y%M&Y3SB8u2??I zGmA^c7IMAY%&e#EB;h?X2ghLZ0U@;b7}PS_^>%8M+emZb30}gKd05XZZkeL<>u5eU z%$ka|*M$cV0zJpE#WwYb=J(kVFNP@xU)g9AJeQIGZ8c?^6{2erGS9;eJt^>@lpx!4 zKk*1LhUkrme&^`Lig=>4^OwVuUHUHd-|K>8Tou2h6R>s+@Mh3tSu?2OeV5vPbxhiC zUNIutqwu@smX6*k;W@0T*!y0qugOqv z#t9jT9k0k=Dc7=keTv*Y0sT<^p0BA)`N?=Ea!!?)Y^?5kxlGdT8{8e7U|~soWoCI_ zmZ>WK`dTUVD3&L@8EcC}M}e-5%7t{JG`O>N(mI#2gSUq4-{vIs2*x(K%|t5n%64+9>egPZNW23knjveg8?`W&YIi zXVUy&rx_qBZwmdp1Y3oGFJ`cPE~1e`XWhK?)|i!6eu8cA)He%)DLIe>4-G!WX7YQ9 z*YY1R3*RNNOH>yClCc)0-m^wnejFDJ&#TP9cHn>>D5b*nEhvvPFnq1RT3soEB3axq zV#7;iN(S2?BY~nT%+UU~%s4UcrAOY@1hfVF_!YFpOb|t!@ujO;JvFT~o*vRi`qGme z>sUI%LD@KyZcY1NGN5t0g{_vbJZ+^27WQ#yFIp5MegeW8^}T{?o@V{6m=lc|}dfxv1^L+Qt6@-wZXZWoMjO1h5qlu%)w)j{U) z^<-zKP74}uu&hC$-Cay>r`fM6^2jC2uFCpqM&Q*KfktbS77*{{DJjv7()?DLi(`Gj zY)nb`tc|UncwCE1S>dkgFqw1`(WNcNIU$i-FrtC@*kgfub8+6CVxS7KJ-&1U(T=Xb zlgc_196jwG+-Y*JU8w?=L-JGa2`iA2+MBJ}+TcB!Q#TAXCn`0kLD|xPri9P%kDZ@+ zuIhcoQqJjB+6CFk)Qq8nIN#DhW|uRclNWVi=pF9#bIz6f=N{4-ZuYjIb2_)6vl_1; zGPUwyKXdxHgcK1dauOA*GyTrMu?n5RT*FN#$iU z+f1A8_jTF}d@bc!5voji<05K8p4mIDf-keb8E6N#I1GYVIjNJ!kgF&@FqS<}3fe&u zoQV`}`W)KynU{8kQs8f(OA+3%F=*GQ?t$qLpPqL=mt!enVvCXS;+Pf{ebzX3<>@}&)R09uk#MEKVPpjG`%lO>e-M8i?J&ekU1(C_>SyE^=zq|E zgAce6XgiC)W_c(yhHy_Oc?21jwD&LiX?m!{VAfIO^FLG`g8VsS$V6bXvA>~~hd@NN z0W`6(Z4B85W>o*jPXmTx5W&LtXwWor4UF-FG!Jy37z#-AEV9m@XvBTY`;UX?z9A_@ z=sU9Q|3wlArZa>FrB2t7D`0_!|IrW;ytI#e{72^d0LGv25Hu3<1dMZtyzvKdcZjU^ z=dV90q9@4wkg_A>0@#k>9te4Tf(-d{o$wvV0LEkipn)+>pg}mhv%md+NQr>nM{tjz z1b_Gh4Mbg@ArHWqbOWH-r!Xuk7;y0y@h}8`5zxedOJo(;;5-DF9x$&>QK`Y*SAPZm zADmFZ2iM4F|CV3u7P$;2|3kl_WSGGFW~j&zQ8++24D*)(ALKa#!1d4YJ{&@S&lEBO z1mM9Y$6sFeK_O#m005GX3t0IB zgxrUq{yu_&_u088$c&IXLcj%#??3aQMgdrWQG%X+^=BlL*`br*Mh$>v#hQGDx)JY% ze1MnQKzDb0)Bs!9hVdb^2E&qrv+SXcMhHEC5f&x;Uu{VtFIWKne>oyTosCf0vB7LM z&=?vyJ75Hcd3^@s32s*aP=gh@04onOksJwq*6z98!wLVe3*!cm!!qcg*&EPA4GRE0 zSd;|-grJE7JYfXyJ)X(-s0@&0Nq`Ux_TQqTfnyv|vHuYCeE~rEGfK( z3R0p9(1gY1?zuwPbODC{B?Sa3MdaTtI5fPN>;fQyq}Tvn!lvb)84$4hM@Wo6y=DU6 zIRFm-JbDk|K};vWXPD6cG^BF@1i*mMD6-+7+cDe$3J>|eeja|o>^==il|k=wnlOUI zpPK}E9h`K$?H0O1m5MVZF-tBlT@V5EtWV`&?Cv7}0?EoH$0TZ|6 zlYC4nt<=~$lapVUde_v=FMaB#Y~dC{!jdiVzf>?Izr!wvPpytW;-``u?TG zIx@TZlvu`wH79tG{7&OQ;8ZVqeR99SzbrB+dJ?jl&j0BfUs~P7jegihe0gy+SD}lXf!Hg@wfQL>@w@Rj@C|BAk z{Z-f+n4c=Ke&&*ka3&XLELC&p=?51LDosjQ(h5iO zgakrEPc|(z(^J0o(C;s_UnUDEH!dMhjL5K&eZX;Kc5~(^A=jebs&Ya<*GIr307ED% zj6o9wo#Kl;J731{b5YE!ApNtVL+gJs~p~MJ4=e% zH56Lpi&t*y%)HA|brac^_^&)V{WJ>At@ z&3;6*&-xzAGUM+^xi``_r~hK5=gJd&S`NZCAjZ^)AYO_IX)|lqnZ0@uHfx09cRorr z?;_}?aKJL2a4Ed*^(lH-Hip?OS<$sY;G?FXl+s!8!C1SMeDDjzJ5;Iw;{${4b0IvS zKmSM0a01y6{Nz{wBAChxwmc^2{~&=vfMDCd5OB_`$M+y~aQ%Nk_YstP5aiYizzch6 zF+l)@hnE&&><_?%tqagU*}=+gP?cU90NDKVPTyw@JkYClLq!7Hx}%~(#sUGdf0ihy z+WZFzQ49y{!YKWBD`ZG!6krd=_aR><7Jv`_Ee`*8iI0Cw(ijWygWX{Qy@WEP1uuJ} zB0`4Z0dHY?18TfTL1I$@YOoufHvkL}%nZPnKROPwk_$kF-FmMBp){Dle7?}!W$_Zg z4b1a*uM++eM+g7*f?64Q)qua{BL8V-?+r}^)T{$o{%!VOGChOfwE%Qs2-PScBP6F2 z086-rKxGX3BOWBO56}dY6e<}vn5_;P)QL2( z`|fyi?O*b|Lz3&cj8&t5PHxx1kcj<_!g>&C9w}B}5>LJ(ro?f=&b8ro@zZWn%xYK&_y1tllg}3lTpCQzHW2_rNQ=EL5moP#b#W4 z#WS~kfpz$psS%B+;Y>}ESBt5Q@{NLwmB>V5p1JTf}nh@Om2{8*$Uw$ZZU+SI+Ms++FdYz-+7 zp$^DurDO(aU7l4r8X2>1>#@qtXnz8y^3z|k%2Hc2ewML-U;HwNNSZ+tR+Z8j;d8@n zc0pKLH-NNQWZ?WQO5=@WIR`(k@~FuTe7L0i_J$_|q}jzI*ZiZ;6ZMl06Fj}OZ>|@C zl&Dh-nUtnfY}xjrW2L{|O&z z1(Ka*`V1D#Qn&g|r281QfImJJp)?G5{b- z!*xU*v@Q*n_3+(6pW{Zn8Hmf*t8TQ=NM3_fp3z^%C(A7r9Eg4Husfx;aUQ^%t$7tU5Ng@^JgBPheV0}k6-8`XwiYIsZ;7<*{zQsYdkjwI30hu zf^YJN@I@~>w5kiY#*nWEZ1(|#`lMI`+j7jqmRGVD#Y54Sf-(aJhC;$)tD_i;V3)bc^_P|#-t4^i}{FY>t(C=e@8Vfgxeo85* z*#pAIFEiaz#>~~WB^!U_o8m}Db4;6eN}?(w&we5nVh!2OlI^RN(xaH?sI11e(hlLR z36w7P3Ihr}>uCz@A1cg8QSIR$Qi*ad?_&6HW~a8QuU^fLV6-fU&a*L2xbUj}E+c}+ zgUMl+ZeDRh?ounqO0S?nwncHC!bZM;Il=CCI(Wjb{h5e^L22qVIBnUJ>Z|}fMy7Vn z@N1xLZ$P*AS!LFdiSoBM1?QjXcW&~4>^K;5mi^!0ni14>UnPYhQG-92r!cB#9dF+~ zWMSgb0qaE(9zA;fzm?iweE>Fo3tj)#rU45u+mR&;$N<^@0igUFpYaDUfXOcbPr)Dr zls~IA1iI?JgBhjJ#f|~I?1xGZQP=_efB~T^;-5AU$3uYEzk%Q}5b86Cz$u{Vzi2?9 zEYe`t(Cj=RaHT&gE(G%qp!i1)2oz}X$J~9$uoUA2Lh~~Npt9qj0$~+=;0IyuWrf_L z1KD65f(k1HaU=lN{TH`C9hwS+?j$@(fgl*>&juSx7{W^l{2SQ%r!PQiAUSMM;CNAJ zz=tS6T!;-FkOJ2HAK(xZ&=Yp&f0~0|2SYQ?W7&WJTs@~rHf)il-a$wEy+#)z@O$=o zI8+F@k_|`#g1$ZA`pT3XwbP#L(X&jm&Do2Kohyt-o`}eZo_hXo5C#LsZ|QNkjN`-m zafPadv$5zj)9_A+8YE~cMpD(w{Cbze20X`%jjVTv@$S|%@AMQrBH33?s&p;?h) zmrkB}+l$rK3K}_`1f-ajQpzY6^g4M-#kC06PT62SOH>)fwZ^&u$v1(0;d84}ishEV zA};%!Ai}h+Q4ObcHPTRLF=J68pS4N4OL;*?g3lx^OSf#D)88C+6S~;D-tjh3C@-Hg zAVzKQVy|6;#9z{KSFC%#lS^*Rii>uu9P3wdC~$~r8a>jUdEZHj!9;JZe3S5a&Bq|} ziO>*g$qvLE+!*N?YFHn>K*BqD%q0Qr)(fS>f*y+QNDIzYRxQ&&&lB+40)1^>{M$Th^8}(o+PHuzut)ZxI+a`y zh!2kC1H$YxsP)1C_6tEJ2Ve357ynp=e_?pwn-FLQF0}w~{tx0IqzffL1tWNW4TfHm zfd@lTF(HW}Kopo!1dYrJgTIRbm;RWc&`WNZbkO2lK1hu;&s6?4;P5?=5Tg7N zmgy!|8c z{j2}C1tTQt6%ZXJ$9>W9Uj!CpRs#q<`1-zp4}34s1L80ex|*5m0MlT(N)H}1sH8X$ zE^{C|Y={2W(1Rq{0~uk`{|C0~3@n5hgi!070Ro+n2^g`5VfYdRgt@4|_r<*rTv4K- z(eKs};J_bo67KWIV*yXVWHHcu;kJmsBXPfZ{|DY04UMR?MgmFybPfVtoc`94JTO9y z23Er?SLpR9Z0PUnyB~OBW�VBo3Gs_-6zj;F+<|UBUB2VEaE)%K$!%g*q1*!N5QH z=McCQpbBjGpi|8Nu8u<`hNPzhWnhDS4}?Ie5yIqy>LPsD{eK4w{4E|@-WZ$7r`w7qo(Y_9-3wtmg#Cf2goCuvx z%qAcjY#^Yw=fb-5_pyRNZ_I{?{6E-$y_28{Y3eg@@J}rx_L)-d4TVTBki0jFUwXw~H(QOs6= z;8-!=!%Ya*ZK#mfNc8WV zwVsH%*y>w$ws2-?k}`jo`tfX`E*rW}dUHMl`rZs&Mp$~1i!n#;9hcApd>%7Y zE5tk$!BqBKAJk#x=F9VrJPR1N;|ecK)VX(KFT1@2Cr&KoHKl;X~Hx2 zQU$PXd+Jylf!B!wFN&g_f~(1NbUs(C3c~55=c0Z`U-p61M_su(4+y(s^Ctt|F;ZNt zJ~BjKrWNojU9}=iBNntU7$~S1?vxmD;j&JUqEYC33hMNvN#_W+k2K(7O%IB0_<

    HZ>(Q`IC_9EO)7FNR-RP1ruG>$Y@U&@)S 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.0.0-SNAPSHOT com.tractrac.clientmodule - 3.0.7 + 3.0.9 eclipse-plugin From f96f0b82dee6ab03cbb7a2da91c61002b04b65eb Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 12:59:35 +0100 Subject: [PATCH 096/264] fixed bug 2063 --- .../domain/test/mock/MockedTrackedRace.java | 5 +++++ .../com/sap/sailing/domain/base/EventFetcher.java | 5 +++++ .../src/com/sap/sailing/domain/base/Regatta.java | 8 ++++++++ .../sap/sailing/domain/base/impl/RegattaImpl.java | 15 +++++++++++++++ .../sailing/gwt/ui/test/MockedTrackedRace.java | 7 +++++++ .../server/impl/RacingEventServiceImpl.java | 12 +++--------- 6 files changed, 43 insertions(+), 9 deletions(-) create mode 100755 java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index 817943466eb..c2d57a54820 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -13,6 +13,7 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumnListener; @@ -430,6 +431,10 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public void removeListener(RegattaLikeListener listener) { } + + @Override + public void adjustEventToRegattaAssociation(EventFetcher eventFetcher) { + } }; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java new file mode 100755 index 00000000000..98209eaeef1 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.base; + +public interface EventFetcher { + Iterable getAllEvents(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java index 0667d9ade2a..1635e492ca4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java @@ -168,4 +168,12 @@ public interface Regatta extends Named, WithID, IsRegattaLike { * Updates the flag deciding whether to use start time inference. See {@link #useStartTimeInference()}. */ void setUseStartTimeInference(boolean useStartTimeInference); + + /** + * {@link Event} manages an association to its {@link Event#getRegattas() regattas}. When something on the + * regatta changes (in particular, the implicit link to an event through an event's {@link CourseArea} that + * is assigned as this regatta's {@link #setDefaultCourseArea(CourseArea) default course area}), the + * opposite end of the association needs to be maintained on the event's side. + */ + void adjustEventToRegattaAssociation(EventFetcher eventFetcher); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index 8dd579aaac4..2a4244f3444 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -15,6 +15,8 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnInSeries; @@ -551,4 +553,17 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene public void removeListener(RegattaLikeListener listener) { regattaLikeHelper.removeListener(listener); } + + public void adjustEventToRegattaAssociation(EventFetcher eventFetcher) { + CourseArea defaultCourseArea = getDefaultCourseArea(); + for (Event event : eventFetcher.getAllEvents()) { + event.removeRegatta(this); + for (CourseArea courseArea : event.getVenue().getCourseAreas()) { + if (defaultCourseArea != null && courseArea.getId().equals(defaultCourseArea)) { + event.addRegatta(this); + } + } + } + + } } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index bf1191a0747..a8a8aab3666 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -11,6 +11,7 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumnListener; @@ -517,6 +518,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { // TODO Auto-generated method stub } + + @Override + public void adjustEventToRegattaAssociation(EventFetcher eventFetcher) { + // TODO Auto-generated method stub + + } }; } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 20d934eeeea..d31005c3dc2 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -60,6 +60,7 @@ import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.LeaderboardSearchResult; import com.sap.sailing.domain.base.LeaderboardSearchResultBase; @@ -209,7 +210,7 @@ import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper; import com.sap.sse.util.ClearStateTestSupport; public class RacingEventServiceImpl implements RacingEventService, ClearStateTestSupport, RegattaListener, - LeaderboardRegistry, Replicator { + LeaderboardRegistry, Replicator, EventFetcher { private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName()); /** @@ -1789,14 +1790,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes regatta.addSeries(seriesObj); } } - for (Event event : getAllEvents()) { - event.removeRegatta(regatta); - for (CourseArea courseArea : event.getVenue().getCourseAreas()) { - if (newDefaultCourseAreaId != null && courseArea.getId().equals(newDefaultCourseAreaId)) { - event.addRegatta(regatta); - } - } - } + regatta.adjustEventToRegattaAssociation(this); if (regatta.isPersistent()) { mongoObjectFactory.storeRegatta(regatta); } From eed757481cc29c7524e4b7d18d27c6e4770a2ce9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 13:16:09 +0100 Subject: [PATCH 097/264] moved image resources so they come from GWT-compliant locations, avoiding warnings for GWT2.7, avoiding things to break after GWT2.7 --- .../adminconsole/AdminConsoleResources.java | 3 --- ...anagementWidgetWaypointsImagesBarCell.java | 5 ++-- .../DefaultRegattaCreateDialog.java | 6 ++--- ...eviceConfigurationCreateMatcherDialog.java | 5 ++-- .../DeviceConfigurationDetailComposite.java | 5 ++-- .../EventConfigImagesBarCell.java | 3 ++- .../gwt/ui/adminconsole/EventDialog.java | 13 +++++----- .../ui/adminconsole/IgtimiAccountsPanel.java | 4 +-- .../LeaderboardConfigImagesBarCell.java | 3 ++- .../LeaderboardGroupConfigImagesBarCell.java | 3 ++- .../LeaderboardRaceConfigImagesBarCell.java | 3 ++- .../ui/adminconsole/MediaActionBarCell.java | 5 ++-- ...ogTrackingDeviceMappingsImagesBarCell.java | 3 ++- .../RegattaConfigImagesBarCell.java | 3 ++- .../RegattaWithSeriesAndFleetsDialog.java | 5 ++-- .../SeriesConfigImagesBarCell.java | 3 ++- .../gwt/ui/adminconsole/SeriesEditDialog.java | 7 ++---- .../SeriesWithFleetsCreateDialog.java | 3 ++- .../SeriesWithFleetsDefaultCreateDialog.java | 3 ++- .../com/sap/sse/gwt/client/IconResources.java | 13 ++++++++++ .../sse/security/ui/client/IconResources.java | 23 ++++++++---------- .../ui/client/component/UserDetailsView.java | 4 +-- .../ui/{ => client}/images/delete.png | Bin .../security/ui/{ => client}/images/logo.png | Bin .../ui/{ => client}/images/status_blue.png | Bin .../ui/{ => client}/images/status_green.png | Bin .../ui/{ => client}/images/status_grey.png | Bin .../ui/{ => client}/images/status_red.png | Bin .../ui/{ => client}/images/status_yellow.png | Bin .../security/ui/{ => client}/images/user.png | Bin .../ui/{ => client}/images/user_icon.png | Bin .../ui/{ => client}/images/user_small.png | Bin 32 files changed, 66 insertions(+), 59 deletions(-) create mode 100755 java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/delete.png (100%) rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/logo.png (100%) rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/status_blue.png (100%) rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/status_green.png (100%) rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/status_grey.png (100%) rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/status_red.png (100%) rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/status_yellow.png (100%) rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/user.png (100%) rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/user_icon.png (100%) rename java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/{ => client}/images/user_small.png (100%) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java index 8d9424d2202..7dc5b7fbbc7 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java @@ -11,9 +11,6 @@ interface AdminConsoleResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/settingsActionIcon.png") ImageResource settingsActionIcon(); - @Source("com/sap/sse/gwt/client/images/remove.png") - ImageResource removeIcon(); - @Source("com/sap/sailing/gwt/ui/client/images/edit.png") ImageResource editIcon(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java index 6f7e9c87be9..97463fc4117 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidgetWaypointsImagesBarCell.java @@ -2,13 +2,12 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; -import com.google.gwt.core.client.GWT; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class CourseManagementWidgetWaypointsImagesBarCell extends ImagesBarCell { public static final String ACTION_DELETE = "ACTION_DELETE"; - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); private final StringMessages stringMessages; public CourseManagementWidgetWaypointsImagesBarCell(StringMessages stringMessages) { @@ -19,7 +18,7 @@ public class CourseManagementWidgetWaypointsImagesBarCell extends ImagesBarCell @Override protected Iterable getImageSpecs() { ArrayList result = new ArrayList(); - result.add(new ImageSpec(ACTION_DELETE, stringMessages.remove(), makeImagePrototype(resources.removeIcon()))); + result.add(new ImageSpec(ACTION_DELETE, stringMessages.remove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); return result; } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DefaultRegattaCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DefaultRegattaCreateDialog.java index 0c061b9c8eb..0b42afc7fb7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DefaultRegattaCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DefaultRegattaCreateDialog.java @@ -2,7 +2,6 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.List; -import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.TabPanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -12,12 +11,11 @@ import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.ListEditorComposite; public class DefaultRegattaCreateDialog extends AbstractRegattaWithSeriesAndFleetsDialog { - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); - public DefaultRegattaCreateDialog(List existingEvents, RegattaDTO selectedRegatta, SailingServiceAsync sailingService, ErrorReporter errorReporter, StringMessages stringMessages, com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback callback) { @@ -34,7 +32,7 @@ public class DefaultRegattaCreateDialog extends AbstractRegattaWithSeriesAndFlee } protected ListEditorComposite createSeriesEditor(Iterable series) { - return new SeriesWithFleetsDefaultListEditor(series, stringMessages, resources.removeIcon(), /* enableFleetRemoval */ false); + return new SeriesWithFleetsDefaultListEditor(series, stringMessages, IconResources.INSTANCE.removeIcon(), /* enableFleetRemoval */ false); } protected void setupAdditionalWidgetsOnPanel(final VerticalPanel panel) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java index 5614548ea1c..3ba15679621 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java @@ -5,7 +5,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; @@ -20,6 +19,7 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class DeviceConfigurationCreateMatcherDialog extends DataEntryDialog { @@ -63,7 +63,6 @@ public class DeviceConfigurationCreateMatcherDialog extends DataEntryDialog devicesBoxes; @@ -164,7 +163,7 @@ public class DeviceConfigurationCreateMatcherDialog extends DataEntryDialog initialValues = originalConfiguration.allowedCourseAreaNames == null ? Collections . emptyList() : originalConfiguration.allowedCourseAreaNames; - allowedCourseAreasList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseAreas(), resources.removeIcon(), + allowedCourseAreasList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseAreas(), IconResources.INSTANCE.removeIcon(), SuggestedCourseAreaNames.suggestedCourseAreaNames, stringMessages.enterCourseAreaName()); allowedCourseAreasList.setWidth("80%"); allowedCourseAreasList.addValueChangeHandler(dirtyValueMarker); @@ -219,7 +220,7 @@ public class DeviceConfigurationDetailComposite extends Composite { List initialValues = originalConfiguration.byNameDesignerCourseNames == null ? Collections . emptyList() : originalConfiguration.byNameDesignerCourseNames; - courseNamesList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseNames(), resources.removeIcon(), suggestedCourseNames, + courseNamesList = new StringListEditorComposite(initialValues, stringMessages, stringMessages.courseNames(), IconResources.INSTANCE.removeIcon(), suggestedCourseNames, stringMessages.enterCourseName()); courseNamesList.setWidth("80%"); courseNamesList.addValueChangeHandler(dirtyValueMarker); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventConfigImagesBarCell.java index 3c6325ab3b8..d1dd0711f96 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class EventConfigImagesBarCell extends ImagesBarCell { static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -27,7 +28,7 @@ public class EventConfigImagesBarCell extends ImagesBarCell { protected Iterable getImageSpecs() { return Arrays.asList( new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon())) + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon())) ); } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java index cb573c4c8c6..74d0956b7bc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java @@ -8,7 +8,6 @@ import java.util.Date; import java.util.List; import java.util.UUID; -import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; @@ -28,11 +27,11 @@ import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.VenueDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite; public abstract class EventDialog extends DataEntryDialogWithBootstrap { - private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); protected StringMessages stringMessages; protected TextBox nameEntryField; protected TextArea descriptionEntryField; @@ -131,22 +130,22 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap }; courseAreaNameList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), /* suggestValues */ + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ SuggestedCourseAreaNames.suggestedCourseAreaNames, stringMessages.enterCourseAreaName(), 50)); courseAreaNameList.addValueChangeHandler(valueChangeHandler); final List imageSuggestionURLs = Arrays.asList(new String[] { "http://", "https://", "http://www.", "https://www" }); imageURLList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ imageSuggestionURLs, stringMessages.enterImageURL(), 80)); imageURLList.addValueChangeHandler(valueChangeHandler); List videoURLSuggestions = new ArrayList<>(imageSuggestionURLs); videoURLSuggestions.add("http://www.youtube.com/watch?v="); videoURLList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ videoURLSuggestions, stringMessages.enterVideoURL(), 80)); videoURLList.addValueChangeHandler(valueChangeHandler); sponsorImageURLList = new StringListInlineEditorComposite(Collections. emptyList(), - new StringListInlineEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ imageSuggestionURLs, stringMessages.enterSponsorImageURL(), 80)); sponsorImageURLList.addValueChangeHandler(valueChangeHandler); List leaderboardGroupNames = new ArrayList<>(); @@ -154,7 +153,7 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap leaderboardGroupNames.add(leaderboardGroupDTO.getName()); } leaderboardGroupList = new StringConstantsListEditorComposite(Collections. emptyList(), - new StringConstantsListEditorComposite.ExpandedUi(stringMessages, resources.removeIcon(), + new StringConstantsListEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), leaderboardGroupNames, "Select a leaderboard group...")); leaderboardGroupList.addValueChangeHandler(valueChangeHandler); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java index 375a7612bd4..424e67c7971 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java @@ -35,6 +35,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -48,7 +49,6 @@ public class IgtimiAccountsPanel extends FlowPanel { public static class AccountImagesBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); private final StringMessages stringMessages; public AccountImagesBarCell(StringMessages stringMessages) { @@ -64,7 +64,7 @@ public class IgtimiAccountsPanel extends FlowPanel { @Override protected Iterable getImageSpecs() { return Arrays.asList( - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java index f83621faf1a..7778bfe95ff 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class LeaderboardConfigImagesBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -35,7 +36,7 @@ public class LeaderboardConfigImagesBarCell extends ImagesBarCell { new ImageSpec(ACTION_EDIT_SCORES, stringMessages.actionEditScores(), makeImagePrototype(resources.scoresIcon())), new ImageSpec(ACTION_EDIT_COMPETITORS, stringMessages.actionEditCompetitors(), makeImagePrototype(resources.competitorsIcon())), new ImageSpec(ACTION_CONFIGURE_URL, stringMessages.actionConfigureUrl(), makeImagePrototype(resources.settingsActionIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon())), + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon())), new ImageSpec(ACTION_EXPORT_XML, stringMessages.actionExportXML(), makeImagePrototype(resources.exportXMLIcon())), new ImageSpec(ACTION_OPEN_COACH_DASHBOARD, stringMessages.actionOpenDashboard(), makeImagePrototype(resources.openCoachDashboard()))); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigImagesBarCell.java index 8e639715f3b..9f4528eda9a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class LeaderboardGroupConfigImagesBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -26,6 +27,6 @@ public class LeaderboardGroupConfigImagesBarCell extends ImagesBarCell { @Override protected Iterable getImageSpecs() { return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java index 3a938222b06..f37d691c789 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java @@ -8,6 +8,7 @@ import com.sap.sailing.domain.common.LeaderboardType; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.gwt.client.IconResources; public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell { public final static String ACTION_REMOVE = "ACTION_REMOVE"; @@ -35,7 +36,7 @@ public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell { StrippedLeaderboardDTO selectedLeaderboard = selectedLeaderboardProvider.getSelectedLeaderboard(); if (selectedLeaderboard != null && selectedLeaderboard.type != LeaderboardType.RegattaLeaderboard && selectedLeaderboard.type != LeaderboardType.RegattaMetaLeaderboard) { // race columns cannot be removed from a regatta leaderboard; they need to be removed from the regatta instead - result.add(new ImageSpec(ACTION_REMOVE, stringMessages.actionRaceRemove(), makeImagePrototype(resources.removeIcon()))); + result.add(new ImageSpec(ACTION_REMOVE, stringMessages.actionRaceRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } result.add(new ImageSpec(ACTION_REFRESH_RACELOG, stringMessages.refreshRaceLog(), makeImagePrototype(resources.reloadIcon()))); result.add(new ImageSpec(ACTION_SET_STARTTIME, stringMessages.setStartTime(), makeImagePrototype(resources.clockIcon()))); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java index e5542f1aec9..cd2ef26340d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaActionBarCell.java @@ -2,14 +2,13 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.Arrays; -import com.google.gwt.core.client.GWT; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class MediaActionBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); private final StringMessages stringMessages; public MediaActionBarCell(StringMessages stringMessages) { @@ -20,6 +19,6 @@ public class MediaActionBarCell extends ImagesBarCell { @Override protected Iterable getImageSpecs() { return Arrays.asList(new ImageSpec(ACTION_REMOVE, - stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java index d31582aa965..2cc7ce92ca8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingDeviceMappingsImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; +import com.sap.sse.gwt.client.IconResources; public class RaceLogTrackingDeviceMappingsImagesBarCell extends ImagesBarCell { public static final String ACTION_CLOSE = "CLOSE"; @@ -23,7 +24,7 @@ public class RaceLogTrackingDeviceMappingsImagesBarCell extends ImagesBarCell { ArrayList result = new ArrayList(); DeviceMappingDTO mapping = (DeviceMappingDTO) getContext().getKey(); - result.add(new ImageSpec(ACTION_REMOVE, stringMessages.remove(), makeImagePrototype(resources.removeIcon()))); + result.add(new ImageSpec(ACTION_REMOVE, stringMessages.remove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); if (mapping.from == null || mapping.to == null) { result.add(new ImageSpec(ACTION_CLOSE, stringMessages.closeTimeRange(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigImagesBarCell.java index 5ea041d561e..2c24f547e1a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class RegattaConfigImagesBarCell extends ImagesBarCell { static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -26,6 +27,6 @@ public class RegattaConfigImagesBarCell extends ImagesBarCell { @Override protected Iterable getImageSpecs() { return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java index 0ff24e805ba..bd855d46bea 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java @@ -2,7 +2,6 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.List; -import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; @@ -12,10 +11,10 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.ListEditorComposite; public abstract class RegattaWithSeriesAndFleetsDialog extends AbstractRegattaWithSeriesAndFleetsDialog { - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); protected TextBox nameEntryField; protected TextBox boatClassEntryField; @@ -38,7 +37,7 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends AbstractRegattaWi @Override protected ListEditorComposite createSeriesEditor(Iterable series) { - return new SeriesWithFleetsListEditor(series, stringMessages, resources.removeIcon(), isEnableFleetRemoval()); + return new SeriesWithFleetsListEditor(series, stringMessages, IconResources.INSTANCE.removeIcon(), isEnableFleetRemoval()); } protected abstract boolean isEnableFleetRemoval(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesConfigImagesBarCell.java index 839416b3260..2f1263e5921 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesConfigImagesBarCell.java @@ -6,6 +6,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; +import com.sap.sse.gwt.client.IconResources; public class SeriesConfigImagesBarCell extends ImagesBarCell { static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -26,6 +27,6 @@ public class SeriesConfigImagesBarCell extends ImagesBarCell { @Override protected Iterable getImageSpecs() { return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), - new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); + new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(IconResources.INSTANCE.removeIcon()))); } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java index 68b306e0a3e..37c769e6c8a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java @@ -6,7 +6,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import com.google.gwt.core.shared.GWT; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; @@ -31,14 +30,12 @@ import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class SeriesEditDialog extends DataEntryDialog { - - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); - private TextBox seriesNameTextBox; private CheckBox isMedalCheckbox; private CheckBox startWithZeroScoreCheckbox; @@ -204,7 +201,7 @@ public class SeriesEditDialog extends DataEntryDialog { discardThresholdBoxesWidget.setVisible(useSeriesResultDiscardingThresholdsCheckbox.getValue()); additionalWidgetPanel.add(discardThresholdBoxesWidget); - raceNamesEditor = new StringListInlineEditorComposite(getExistingRacesOfSeries(), new RaceNamesEditorUi(regatta, stringMessages, resources.removeIcon(), seriesName)); + raceNamesEditor = new StringListInlineEditorComposite(getExistingRacesOfSeries(), new RaceNamesEditorUi(regatta, stringMessages, IconResources.INSTANCE.removeIcon(), seriesName)); raceNamesEditor.ensureDebugId("RaceNamesStringListEditorComposite"); raceNamesEditor.addValueChangeHandler(new ValueChangeHandler>() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java index dd5031d3b76..7343855a644 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsCreateDialog.java @@ -20,6 +20,7 @@ import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.ListEditorComposite; import com.sap.sse.gwt.client.dialog.DataEntryDialog; @@ -146,7 +147,7 @@ public class SeriesWithFleetsCreateDialog extends DataEntryDialog { protected void initializeFleetListComposite(StringMessages stringMessages) { fleetListComposite = new FleetListEditorComposite(Arrays.asList(new FleetDTO( - LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)), stringMessages, resources.removeIcon()); + LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)), stringMessages, IconResources.INSTANCE.removeIcon()); fleetListComposite.ensureDebugId("FleetListEditorComposite"); fleetListComposite.addValueChangeHandler(new ValueChangeHandler>() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java index 12c7011cd36..2ba1ac26a8b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesWithFleetsDefaultCreateDialog.java @@ -12,6 +12,7 @@ import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sse.gwt.client.IconResources; /** * A variant of the dialog used to create a series within a regatta that has fleets. This variant is actually more of a @@ -49,7 +50,7 @@ public class SeriesWithFleetsDefaultCreateDialog extends SeriesWithFleetsCreateD protected void initializeFleetListComposite(StringMessages stringMessages) { fleetListComposite = new FleetListEditableEditorComposite(Arrays.asList(new FleetDTO( - LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)), stringMessages, resources.removeIcon()); + LeaderboardNameConstants.DEFAULT_FLEET_NAME, 0, null)), stringMessages, IconResources.INSTANCE.removeIcon()); fleetListComposite.ensureDebugId("FleetListEditableEditorComposite"); fleetListComposite.addValueChangeHandler(new ValueChangeHandler>() { @Override diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java new file mode 100755 index 00000000000..dd1a3dcf9ec --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/IconResources.java @@ -0,0 +1,13 @@ +package com.sap.sse.gwt.client; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.ImageResource; + +public interface IconResources extends ClientBundle { + + public static final IconResources INSTANCE = GWT.create(IconResources.class); + + @Source("images/remove.png") + ImageResource removeIcon(); +} diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/IconResources.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/IconResources.java index 489510d0ea0..2e82d56e3bd 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/IconResources.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/IconResources.java @@ -8,36 +8,33 @@ public interface IconResources extends ClientBundle { public static final IconResources INSTANCE = GWT.create(IconResources.class); - @Source("com/sap/sse/security/ui/images/logo.png") + @Source("images/logo.png") ImageResource sapLogo(); - @Source("com/sap/sse/security/ui/images/user.png") + @Source("images/user.png") ImageResource user(); - @Source("com/sap/sse/security/ui/images/user_small.png") + @Source("images/user_small.png") ImageResource userSmall(); - @Source("com/sap/sse/security/ui/images/user_icon.png") + @Source("images/user_icon.png") ImageResource userIcon(); - @Source("com/sap/sse/security/ui/images/status_red.png") + @Source("images/status_red.png") ImageResource statusRed(); - @Source("com/sap/sse/security/ui/images/status_green.png") + @Source("images/status_green.png") ImageResource statusGreen(); - @Source("com/sap/sse/security/ui/images/status_yellow.png") + @Source("images/status_yellow.png") ImageResource statusYellow(); - @Source("com/sap/sse/security/ui/images/status_blue.png") + @Source("images/status_blue.png") ImageResource statusBlue(); - @Source("com/sap/sse/security/ui/images/status_grey.png") + @Source("images/status_grey.png") ImageResource statusGrey(); - @Source("com/sap/sse/security/ui/images/delete.png") + @Source("images/delete.png") ImageResource delete(); - - @Source("com/sap/sse/gwt/client/images/remove.png") - ImageResource remove(); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java index 839ca489f9d..6ee94c0fe1e 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java @@ -66,7 +66,7 @@ public class UserDetailsView extends FlowPanel { for (DefaultPermissions defaultPermission : DefaultPermissions.values()) { defaultPermissionNames.add(defaultPermission.getPermissionname()); } - rolesEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getRoles(), stringMessages, IconResources.INSTANCE.remove(), defaultRoleNames, + rolesEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getRoles(), stringMessages, com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), defaultRoleNames, stringMessages.enterRoleName()); rolesEditor.addValueChangeHandler(new ValueChangeHandler>() { @Override @@ -95,7 +95,7 @@ public class UserDetailsView extends FlowPanel { })); } }); - permissionsEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getPermissions(), stringMessages, IconResources.INSTANCE.remove(), defaultPermissionNames, + permissionsEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getPermissions(), stringMessages, com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), defaultPermissionNames, stringMessages.enterPermissionName()); permissionsEditor.addValueChangeHandler(new ValueChangeHandler>() { @Override diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/delete.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/delete.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/delete.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/delete.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/logo.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/logo.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/logo.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/logo.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_blue.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_blue.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_blue.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_blue.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_green.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_green.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_green.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_green.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_grey.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_grey.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_grey.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_grey.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_red.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_red.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_red.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_red.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_yellow.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_yellow.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/status_yellow.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/status_yellow.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user_icon.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user_icon.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user_icon.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user_icon.png diff --git a/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user_small.png b/java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user_small.png similarity index 100% rename from java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/images/user_small.png rename to java/com.sap.sse.security.ui/src/main/resources/com/sap/sse/security/ui/client/images/user_small.png From 412ee455ba356943895e4b6177e67ebe829ae61b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 13:17:57 +0100 Subject: [PATCH 098/264] un-ignore breaking test on branch 2589 again --- .../src/com/sap/sse/security/test/SecurityResourceTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java index 80fea90a3f8..f2d13ca7661 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -11,7 +11,6 @@ import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import com.sap.sse.security.SecurityService; @@ -42,7 +41,6 @@ public class SecurityResourceTest { }; } - @Ignore("On master branch ignore this until it's ready and green") @Test public void createAccessTokenAndAuthenticate() throws ParseException { String responseJsonString = (String) servlet.accessToken("admin", "admin").getEntity(); From dabd2d3a98c036a310491411bfad69b5ed9a1778 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 13:52:05 +0100 Subject: [PATCH 099/264] fixed initialization of SecurityResourceTest whose first test around access token retrieval is now green --- .../src/com/sap/sse/security/test/LoginTest.java | 2 +- .../sse/security/test/SecurityResourceTest.java | 4 +++- .../src/com/sap/sse/security/impl/Activator.java | 6 +++++- .../sse/security/impl/SecurityServiceImpl.java | 16 ++++++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java index 71d9f9da6f1..62e5d83697e 100644 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/LoginTest.java @@ -40,7 +40,7 @@ public class LoginTest { UsernamePasswordRealm.setTestUserStore(store); Activator.setTestUserStore(store); Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // to enable shiro to find classes from com.sap.sse.security - new SecurityServiceImpl(store, /* mailProperties */ new Properties()); + Activator.setSecurityService(new SecurityServiceImpl(store, /* mailProperties */ new Properties())); } @Test diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java index f2d13ca7661..8ae90a36df6 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -15,6 +15,7 @@ import org.junit.Test; import com.sap.sse.security.SecurityService; import com.sap.sse.security.UsernamePasswordRealm; +import com.sap.sse.security.impl.Activator; import com.sap.sse.security.impl.SecurityServiceImpl; import com.sap.sse.security.jaxrs.api.SecurityResource; import com.sap.sse.security.shared.MailException; @@ -28,8 +29,9 @@ public class SecurityResourceTest { @Before public void setUp() throws UserManagementException, MailException { final UserStoreImpl store = new UserStoreImpl(); + Activator.setTestUserStore(store); UsernamePasswordRealm.setTestUserStore(store); - service = new SecurityServiceImpl(store, /* mailProperties */ new Properties()); + service = new SecurityServiceImpl(store, /* mailProperties */ new Properties(), /* setAsActivatorSecurityService */ true); SecurityUtils.setSecurityManager(service.getSecurityManager()); Session session = SecurityUtils.getSubject().getSession(); assertNotNull(session); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java index f8c54dfdbe5..d23d75ff6ed 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/Activator.java @@ -42,6 +42,10 @@ public class Activator implements BundleActivator { UsernamePasswordRealm.setTestUserStore(theTestUserStore); } + public static void setSecurityService(SecurityService securityService) { + Activator.securityService = securityService; + } + public static BundleContext getContext() { return context; } @@ -75,7 +79,7 @@ public class Activator implements BundleActivator { } private void createAndRegisterSecurityService(UserStore store, Properties mailProperties) { - securityService = new SecurityServiceImpl(store, mailProperties); + securityService = new SecurityServiceImpl(store, mailProperties, /* setAsActivatorSecurityService */ true); registration = context.registerService(SecurityService.class.getName(), securityService, null); final Dictionary replicableServiceProperties = new Hashtable<>(); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index 73bf80d6d7d..6954dc20a1e 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -134,8 +134,24 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica * @param mailProperties must not be null */ public SecurityServiceImpl(UserStore store, Properties mailProperties) { + this(store, mailProperties, /* setAsActivatorTestSecurityService */ false); + } + + /** + * @param setAsActivatorSecurityService + * when true, the {@link Activator#setSecurityService(com.sap.sse.security.SecurityService)} + * will be called with this new instance as argument so that the cache manager can already be accessed + * when the security manager is created. {@link ReplicatingCacheManager#getCache(String)} fetches the + * activator's security service and passes it to the cache entries created. They need it, in turn, for + * replication. + * + */ + public SecurityServiceImpl(UserStore store, Properties mailProperties, boolean setAsActivatorSecurityService) { assert mailProperties != null; logger.info("Initializing Security Service with user store " + store+" and mail properties "+mailProperties); + if (setAsActivatorSecurityService) { + Activator.setSecurityService(this); + } operationsSentToMasterForReplication = new HashSet<>(); cacheManager = new ReplicatingCacheManager(); this.operationExecutionListeners = new ConcurrentHashMap<>(); From d041cece0cba0b169f863cf89e74bd5bcdd83b19 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 14:22:33 +0100 Subject: [PATCH 100/264] implemented AccessTokenRealm and added a first simple test case --- .../src/main/resources/shiro.ini | 4 +- .../security/test/SecurityResourceTest.java | 17 ++++++++- .../src/main/resources/shiro.ini | 4 +- java/com.sap.sse.security/resources/shiro.ini | 4 +- .../src/com/sap/sse/security/AccessToken.java | 2 +- .../sap/sse/security/AccessTokenRealm.java | 38 +++++++++++++++++++ .../com/sap/sse/security/SecurityService.java | 2 + .../SimpleSaltedAuthenticationInfo.java | 8 ++-- .../security/impl/SecurityServiceImpl.java | 17 +++++++++ .../security/jaxrs/api/SecurityResource.java | 2 +- 10 files changed, 84 insertions(+), 14 deletions(-) create mode 100755 java/com.sap.sse.security/src/com/sap/sse/security/AccessTokenRealm.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini index 2958f1e5360..d9123231d8e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini @@ -14,10 +14,10 @@ credentialsMatcher.hashIterations = 1024 upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher - oauthRealm = com.sap.sse.security.OAuthRealm +accessTokenRealm = com.sap.sse.security.AccessTokenRealm -securityManager.realms = $upRealm, $oauthRealm +securityManager.realms = $upRealm, $oauthRealm, $accessTokenRealm sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java index 8ae90a36df6..e33a22af9fe 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -2,9 +2,12 @@ package com.sap.sse.security.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.util.Properties; +import javax.ws.rs.core.Response; + import org.apache.shiro.SecurityUtils; import org.apache.shiro.session.Session; import org.json.simple.JSONObject; @@ -43,12 +46,22 @@ public class SecurityResourceTest { }; } - @Test - public void createAccessTokenAndAuthenticate() throws ParseException { + private String createAccessToken() throws ParseException { String responseJsonString = (String) servlet.accessToken("admin", "admin").getEntity(); JSONObject responseJson = (JSONObject) new JSONParser().parse(responseJsonString); assertEquals("admin", responseJson.get("username")); String accessToken = (String) responseJson.get("access_token"); assertNotNull(accessToken); + return accessToken; + } + + @Test + public void createAccessTokenAndAuthenticate() throws ParseException { + String accessToken = createAccessToken(); + Response response = servlet.authenticate(accessToken); + JSONObject responseJson = (JSONObject) new JSONParser().parse((String) response.getEntity()); + assertEquals("admin", responseJson.get("username")); + assertTrue(SecurityUtils.getSubject().isAuthenticated()); + assertEquals("admin", SecurityUtils.getSubject().getPrincipal()); } } diff --git a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini index 8e7d14f8c4b..06ec02bbf2f 100755 --- a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini @@ -6,10 +6,10 @@ credentialsMatcher.hashIterations = 1024 upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher - oauthRealm = com.sap.sse.security.OAuthRealm +accessTokenRealm = com.sap.sse.security.AccessTokenRealm -securityManager.realms = $upRealm, $oauthRealm +securityManager.realms = $upRealm, $oauthRealm, $accessTokenRealm sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index d751a822a12..0be28cb3e39 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -9,10 +9,10 @@ credentialsMatcher.hashIterations = 1024 upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher - oauthRealm = com.sap.sse.security.OAuthRealm +accessTokenRealm = com.sap.sse.security.AccessTokenRealm -securityManager.realms = $upRealm, $oauthRealm +securityManager.realms = $upRealm, $oauthRealm, $accessTokenRealm sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java b/java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java index ab826487576..bacf4debc85 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java @@ -28,7 +28,7 @@ public class AccessToken implements AuthenticationToken { } @Override - public Object getCredentials() { + public String getCredentials() { return accessToken; } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AccessTokenRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/AccessTokenRealm.java new file mode 100755 index 00000000000..c920f0f7b80 --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/AccessTokenRealm.java @@ -0,0 +1,38 @@ +package com.sap.sse.security; + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SaltedAuthenticationInfo; + +/** + * A realm that authenticates users by an access token which has previously been obtained through + * {@link SecurityService#createAccessToken(String)}. + * + * @author Axel Uhl (D043530) + * + */ +public class AccessTokenRealm extends AbstractUserStoreBasedRealm { + + public AccessTokenRealm() { + super(); + } + + @Override + public boolean supports(AuthenticationToken token) { + return token instanceof AccessToken; + } + + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + AccessToken accessToken = (AccessToken) token; + final User user = getUserStore().getUserByAccessToken(accessToken.getCredentials()); + if (user == null) { + return null; + } + // return salted credentials + SaltedAuthenticationInfo sai = new SimpleSaltedAuthenticationInfo(user.getName(), accessToken.getCredentials(), /* salt */ null); + return sai; + } + +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index 43a3bcab1e4..31f1b9841c2 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -157,4 +157,6 @@ public interface SecurityService extends ReplicableWithObjectInputStream Date: Fri, 13 Feb 2015 14:40:50 +0100 Subject: [PATCH 101/264] added tests for REST API permission checking --- .../security/test/SecurityResourceTest.java | 19 +++++++++++++++++-- .../mongodb/impl/MongoObjectFactoryImpl.java | 1 - 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java index e33a22af9fe..768ed6f072f 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -1,6 +1,7 @@ package com.sap.sse.security.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -10,6 +11,7 @@ import javax.ws.rs.core.Response; import org.apache.shiro.SecurityUtils; import org.apache.shiro.session.Session; +import org.apache.shiro.subject.Subject; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; @@ -23,6 +25,7 @@ import com.sap.sse.security.impl.SecurityServiceImpl; import com.sap.sse.security.jaxrs.api.SecurityResource; import com.sap.sse.security.shared.MailException; import com.sap.sse.security.shared.UserManagementException; +import com.sap.sse.security.userstore.mongodb.PersistenceFactory; import com.sap.sse.security.userstore.mongodb.UserStoreImpl; public class SecurityResourceTest { @@ -31,6 +34,7 @@ public class SecurityResourceTest { @Before public void setUp() throws UserManagementException, MailException { + PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().getDatabase().dropDatabase(); final UserStoreImpl store = new UserStoreImpl(); Activator.setTestUserStore(store); UsernamePasswordRealm.setTestUserStore(store); @@ -44,6 +48,9 @@ public class SecurityResourceTest { return service; } }; + store.addPermissionForUser("admin", "can do"); + store.addPermissionForUser("admin", "event:view:*"); + store.addPermissionForUser("admin", "event:edit:123"); } private String createAccessToken() throws ParseException { @@ -61,7 +68,15 @@ public class SecurityResourceTest { Response response = servlet.authenticate(accessToken); JSONObject responseJson = (JSONObject) new JSONParser().parse((String) response.getEntity()); assertEquals("admin", responseJson.get("username")); - assertTrue(SecurityUtils.getSubject().isAuthenticated()); - assertEquals("admin", SecurityUtils.getSubject().getPrincipal()); + final Subject subject = SecurityUtils.getSubject(); + assertTrue(subject.isAuthenticated()); + assertEquals("admin", subject.getPrincipal()); + assertTrue(subject.isPermitted("can do")); + assertFalse(subject.isPermitted("can't do")); + assertTrue(subject.isPermitted("event:view:999")); + assertTrue(subject.isPermitted("event:edit:123")); + assertFalse(subject.isPermitted("event:edit:234")); + subject.logout(); + assertFalse(subject.isAuthenticated()); } } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java index d94e600e0ae..41ebcee5c8f 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java @@ -52,7 +52,6 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { DBCollection usersCollection = db.getCollection(CollectionNames.USERS.name()); DBObject dbUser = new BasicDBObject(); dbUser.put(FieldNames.User.NAME.name(), user.getName()); - usersCollection.remove(dbUser); } From 7bc5abcbf4a0cabf218f4f139a629238213aadc6 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 15:00:20 +0100 Subject: [PATCH 102/264] trying to avoid Android project warnings by adding a DOCTYPE --- mobile/com.sap.sailing.racecommittee.app/lint.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/com.sap.sailing.racecommittee.app/lint.xml b/mobile/com.sap.sailing.racecommittee.app/lint.xml index 12d0844263e..5320a5bc7ac 100644 --- a/mobile/com.sap.sailing.racecommittee.app/lint.xml +++ b/mobile/com.sap.sailing.racecommittee.app/lint.xml @@ -1,4 +1,5 @@ + From 4607abfe3cece47969b453c6980c89a61a7d4d9b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 15:05:42 +0100 Subject: [PATCH 103/264] moved VerticalTabLayoutPanel.css to its Java class, avoiding more of the "Resource 'com/sap/sse/gwt/resources/VerticalTabLayoutPanel.css' was located via ClassLoader" warnings --- .../gwt/{resources => client/panels}/VerticalTabLayoutPanel.css | 0 .../com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.java | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename java/com.sap.sse.gwt/src/com/sap/sse/gwt/{resources => client/panels}/VerticalTabLayoutPanel.css (100%) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/VerticalTabLayoutPanel.css b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.css similarity index 100% rename from java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/VerticalTabLayoutPanel.css rename to java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.css diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.java index c30cf12450e..c9c9a767128 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/VerticalTabLayoutPanel.java @@ -175,7 +175,7 @@ public class VerticalTabLayoutPanel extends ResizeComposite implements HasWidget } interface VerticalTabLayoutPanelResources extends ClientBundle { - @Source("com/sap/sse/gwt/resources/VerticalTabLayoutPanel.css") + @Source("VerticalTabLayoutPanel.css") CssResource verticalTabPanelStyles(); } From 3977fe10014532e0f9923436687808478b978fdb Mon Sep 17 00:00:00 2001 From: fmittag Date: Fri, 13 Feb 2015 15:46:03 +0100 Subject: [PATCH 104/264] improved margins for leaderboard styling hack --- .../home/client/place/event/header/CompactEventHeader.ui.xml | 2 +- .../place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml | 2 +- .../client/place/event/oldleaderboard/OldLeaderboard.ui.xml | 2 +- .../place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml index 4a134f1f2d9..7e3fb498de0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/header/CompactEventHeader.ui.xml @@ -9,7 +9,7 @@
    -
    +
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml index 774c6fd61bd..68d7122a180 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldcompetitorcharts/OldCompetitorCharts.ui.xml @@ -7,7 +7,7 @@ -
    +

    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml index 56cdf5996d4..56f6dc4dbf8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldleaderboard/OldLeaderboard.ui.xml @@ -7,7 +7,7 @@ -
    +

    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml index b5061cfb674..6bf4b5d7afd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/oldmultileaderboard/OldMultiLeaderboard.ui.xml @@ -7,7 +7,7 @@ -
    +

    From 021b4a96ac392096481ef0c75d658e2aa73994c5 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 15:59:24 +0100 Subject: [PATCH 105/264] replaced UserRoles.adminitsrator by DefaultRoles.ADMIN --- .../META-INF/MANIFEST.MF | 3 +- .../SailingAdminConsoleFeatures.java | 41 ++++++++++--------- .../media/MediaPlayerManagerComponent.java | 3 +- .../gwt/ui/server/security/Permissions.java | 5 +++ .../sailing/gwt/ui/server/security/Roles.java | 18 ++++++++ .../SailingPermissionsForRoleProvider.java | 23 +++++++++++ .../gwt/ui/usermanagement/UserRoles.java | 3 -- .../src/main/resources/shiro.ini | 4 ++ .../sap/sse/security/shared/DefaultRoles.java | 3 +- .../src/com/sap/sse/security/shared/Role.java | 9 ++++ .../security/AbstractUserStoreBasedRealm.java | 13 ++++++ .../security/PermissionsForRoleProvider.java | 14 +++++++ 12 files changed, 113 insertions(+), 26 deletions(-) create mode 100755 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permissions.java create mode 100755 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java create mode 100755 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/SailingPermissionsForRoleProvider.java create mode 100755 java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java create mode 100755 java/com.sap.sse.security/src/com/sap/sse/security/PermissionsForRoleProvider.java diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 0f82a9aec59..26eb1466b06 100644 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -45,7 +45,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.operationaltransformation, com.sap.sse, com.sap.sse.security.common, - com.sap.sse.filestorage + com.sap.sse.filestorage, + org.apache.shiro.core;bundle-version="1.2.2" Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy Import-Package: com.sap.sailing.polars, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java index 7d1fe18dafb..6cbfe82d1ad 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java @@ -2,26 +2,27 @@ package com.sap.sailing.gwt.ui.adminconsole; import com.sap.sailing.gwt.ui.usermanagement.UserRoles; import com.sap.sse.gwt.adminconsole.AdminConsoleFeatures; +import com.sap.sse.security.shared.DefaultRoles; public interface SailingAdminConsoleFeatures { - final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures("MANAGE_EVENTS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures("MANAGE_REGATTAS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures("MANAGE_TRACKED_RACES", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures("SHOW_TRACKED_RACES", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_RACELOG_TRACKING = new AdminConsoleFeatures("MANAGE_RACELOG_TRACKING", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_ALL_COMPETITORS = new AdminConsoleFeatures("MANAGE_ALL_COMPETITORS", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures("MANAGE_COURSE_LAYOUT", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures("MANAGE_WIND", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures("MANAGE_IGTIMI_ACCOUNTS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures("MANAGE_LEADERBOARDS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures("MANAGE_LEADERBOARD_GROUPS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_RESULT_IMPORT_URLS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_STRUCTURE_IMPORT_URLS", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures("MANAGE_MEDIA", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_SAILING_SERVER_INSTANCES = new AdminConsoleFeatures("MANAGE_SAILING_SERVER_INSTANCES", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_REPLICATION = new AdminConsoleFeatures("MANAGE_REPLICATION", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_MASTERDATA_IMPORT = new AdminConsoleFeatures("MANAGE_MASTERDATA_IMPORT", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures("MANAGE_DEVICE_CONFIGURATION", UserRoles.eventmanager.getRolename(), UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_USERS = new AdminConsoleFeatures("MANAGE_USERS", UserRoles.administrator.getRolename()); - final AdminConsoleFeatures MANAGE_FILE_STORAGE = new AdminConsoleFeatures("MANAGE_FILE_STORAGE", UserRoles.administrator.getRolename()); + final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures("MANAGE_EVENTS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures("MANAGE_REGATTAS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures("MANAGE_TRACKED_RACES", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures("SHOW_TRACKED_RACES", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_RACELOG_TRACKING = new AdminConsoleFeatures("MANAGE_RACELOG_TRACKING", DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_ALL_COMPETITORS = new AdminConsoleFeatures("MANAGE_ALL_COMPETITORS", DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures("MANAGE_COURSE_LAYOUT", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures("MANAGE_WIND", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures("MANAGE_IGTIMI_ACCOUNTS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures("MANAGE_LEADERBOARDS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures("MANAGE_LEADERBOARD_GROUPS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_RESULT_IMPORT_URLS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_STRUCTURE_IMPORT_URLS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures("MANAGE_MEDIA", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_SAILING_SERVER_INSTANCES = new AdminConsoleFeatures("MANAGE_SAILING_SERVER_INSTANCES", DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_REPLICATION = new AdminConsoleFeatures("MANAGE_REPLICATION", DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_MASTERDATA_IMPORT = new AdminConsoleFeatures("MANAGE_MASTERDATA_IMPORT", DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures("MANAGE_DEVICE_CONFIGURATION", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_USERS = new AdminConsoleFeatures("MANAGE_USERS", DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_FILE_STORAGE = new AdminConsoleFeatures("MANAGE_FILE_STORAGE", DefaultRoles.ADMIN.getRolename()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java index b38c75cdfd1..45f8f4a550b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java @@ -49,6 +49,7 @@ import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.player.Timer.PlayStates; import com.sap.sse.gwt.client.useragent.UserAgentDetails; import com.sap.sse.gwt.client.useragent.UserAgentDetails.AgentTypes; +import com.sap.sse.security.shared.DefaultRoles; import com.sap.sse.security.ui.client.UserService; import com.sap.sse.security.ui.shared.UserDTO; @@ -478,7 +479,7 @@ public class MediaPlayerManagerComponent implements Component, PlayStateLi }; final VideoSynchPlayer videoPlayer; final UserDTO currentUser = userService.getCurrentUser(); - boolean showSynchControls = currentUser != null && (currentUser.hasRole(UserRoles.administrator.getRolename()) || + boolean showSynchControls = currentUser != null && (currentUser.hasRole(DefaultRoles.ADMIN.getRolename()) || currentUser.hasRole(UserRoles.eventmanager.getRolename()) || currentUser.hasRole(UserRoles.mediaeditor.getRolename())); if (videoTrack.isYoutube()) { videoPlayer = new VideoYoutubePlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permissions.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permissions.java new file mode 100755 index 00000000000..ac4a997b04e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permissions.java @@ -0,0 +1,5 @@ +package com.sap.sailing.gwt.ui.server.security; + +public enum Permissions { + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java new file mode 100755 index 00000000000..32f51644efb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java @@ -0,0 +1,18 @@ +package com.sap.sailing.gwt.ui.server.security; + +import com.sap.sse.security.shared.Role; + +public enum Roles implements Role { + EVENT_MANAGER("event_manager"); + + private Roles(String rolename) { + this.rolename = rolename; + } + + @Override + public String getRolename() { + return rolename; + } + + private final String rolename; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/SailingPermissionsForRoleProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/SailingPermissionsForRoleProvider.java new file mode 100755 index 00000000000..3c343fa2fec --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/SailingPermissionsForRoleProvider.java @@ -0,0 +1,23 @@ +package com.sap.sailing.gwt.ui.server.security; + +import java.util.Collections; + +import org.apache.shiro.authz.Permission; +import org.apache.shiro.authz.permission.WildcardPermission; + +import com.sap.sse.security.PermissionsForRoleProvider; +import com.sap.sse.security.shared.DefaultRoles; + +public class SailingPermissionsForRoleProvider implements PermissionsForRoleProvider { + @Override + public Iterable getPermissions(String role) { + final Iterable result; + if (DefaultRoles.ADMIN.getRolename().equals(role)) { + return Collections.singletonList(new WildcardPermission("*")); + } else { + result = Collections.emptyList(); + } + return result; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java index a6c432b87fc..ff5cc78c265 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java @@ -1,9 +1,6 @@ package com.sap.sailing.gwt.ui.usermanagement; -import com.sap.sse.security.shared.DefaultRoles; - public enum UserRoles { - administrator(DefaultRoles.ADMIN.getRolename()), spectator("spectator"), moderator("moderator"), sailor("sailor"), diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini index d9123231d8e..b3d07ec9f34 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini @@ -12,10 +12,14 @@ credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 +permissionsForRoleProvider = com.sap.sailing.gwt.ui.server.security.SailingPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher +upRealm.permissionsForRoleProvider = $permissionsForRoleProvider oauthRealm = com.sap.sse.security.OAuthRealm +oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider accessTokenRealm = com.sap.sse.security.AccessTokenRealm +accessTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider securityManager.realms = $upRealm, $oauthRealm, $accessTokenRealm diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultRoles.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultRoles.java index 8afd245f2ba..56e676ac6f9 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultRoles.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultRoles.java @@ -1,12 +1,13 @@ package com.sap.sse.security.shared; -public enum DefaultRoles { +public enum DefaultRoles implements Role { ADMIN("admin"); private DefaultRoles(String rolename) { this.rolename = rolename; } + @Override public String getRolename() { return rolename; } diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java new file mode 100755 index 00000000000..a238aecb09f --- /dev/null +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Role.java @@ -0,0 +1,9 @@ +package com.sap.sse.security.shared; + +public interface Role { + String getRolename(); + + String name(); + + int ordinal(); +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java index 81453ea9614..bb08ac9eb29 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java @@ -11,6 +11,7 @@ import java.util.logging.Logger; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.Permission; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; @@ -24,6 +25,7 @@ import com.sap.sse.security.shared.UserManagementException; public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { private static final Logger logger = Logger.getLogger(AbstractUserStoreBasedRealm.class.getName()); private final Future userStore; + private PermissionsForRoleProvider permissionsForRoleProvider; /** * In a non-OSGi test environment, having Shiro instantiate this class with a default constructor makes it difficult @@ -46,6 +48,10 @@ public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { userStore = null; } } + + public void setPermissionsForRoleProvider(PermissionsForRoleProvider permissionsForRoleProvider) { + this.permissionsForRoleProvider = permissionsForRoleProvider; + } private Future createUserStoreFuture(BundleContext bundleContext) { final ServiceTracker tracker = new ServiceTracker<>(bundleContext, UserStore.class, /* customizer */ null); @@ -101,6 +107,13 @@ public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { String username = r.toString(); try { Util.addAll(getUserStore().getRolesFromUser(username), roles); + if (permissionsForRoleProvider != null) { + for (String role : roles) { + for (Permission permission : permissionsForRoleProvider.getPermissions(role)) { + ai.addObjectPermission(permission); + } + } + } Util.addAll(getUserStore().getPermissionsFromUser(username), permissions); } catch (UserManagementException e) { throw new AuthenticationException(e.getMessage()); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/PermissionsForRoleProvider.java b/java/com.sap.sse.security/src/com/sap/sse/security/PermissionsForRoleProvider.java new file mode 100755 index 00000000000..a30534d9147 --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/PermissionsForRoleProvider.java @@ -0,0 +1,14 @@ +package com.sap.sse.security; + +import org.apache.shiro.authz.Permission; + +/** + * Roles can imply permissions. For example, an application may define that the role "admin" can do everything by + * returning a "*" permission from {@link #getPermissions(String)}. + * + * @author Axel Uhl (D043530) + * + */ +public interface PermissionsForRoleProvider { + Iterable getPermissions(String role); +} From 98dc00dd21fb255a7e47b0e6f49376176eb0ee74 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 16:01:11 +0100 Subject: [PATCH 106/264] consolidated UserRoles and Roles --- .../SailingAdminConsoleFeatures.java | 28 +++++++++---------- .../media/MediaPlayerManagerComponent.java | 4 +-- .../sailing/gwt/ui/server/security/Roles.java | 12 +++++--- .../gwt/ui/usermanagement/UserRoles.java | 20 ------------- 4 files changed, 24 insertions(+), 40 deletions(-) mode change 100755 => 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java index 6cbfe82d1ad..b7b64309796 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java @@ -1,28 +1,28 @@ package com.sap.sailing.gwt.ui.adminconsole; -import com.sap.sailing.gwt.ui.usermanagement.UserRoles; +import com.sap.sailing.gwt.ui.server.security.Roles; import com.sap.sse.gwt.adminconsole.AdminConsoleFeatures; import com.sap.sse.security.shared.DefaultRoles; public interface SailingAdminConsoleFeatures { - final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures("MANAGE_EVENTS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures("MANAGE_REGATTAS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures("MANAGE_TRACKED_RACES", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures("SHOW_TRACKED_RACES", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures("MANAGE_EVENTS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures("MANAGE_REGATTAS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures("MANAGE_TRACKED_RACES", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures("SHOW_TRACKED_RACES", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); final AdminConsoleFeatures MANAGE_RACELOG_TRACKING = new AdminConsoleFeatures("MANAGE_RACELOG_TRACKING", DefaultRoles.ADMIN.getRolename()); final AdminConsoleFeatures MANAGE_ALL_COMPETITORS = new AdminConsoleFeatures("MANAGE_ALL_COMPETITORS", DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures("MANAGE_COURSE_LAYOUT", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures("MANAGE_WIND", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures("MANAGE_IGTIMI_ACCOUNTS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures("MANAGE_LEADERBOARDS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures("MANAGE_LEADERBOARD_GROUPS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_RESULT_IMPORT_URLS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_STRUCTURE_IMPORT_URLS", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures("MANAGE_MEDIA", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures("MANAGE_COURSE_LAYOUT", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures("MANAGE_WIND", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures("MANAGE_IGTIMI_ACCOUNTS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures("MANAGE_LEADERBOARDS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures("MANAGE_LEADERBOARD_GROUPS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_RESULT_IMPORT_URLS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_STRUCTURE_IMPORT_URLS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures("MANAGE_MEDIA", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); final AdminConsoleFeatures MANAGE_SAILING_SERVER_INSTANCES = new AdminConsoleFeatures("MANAGE_SAILING_SERVER_INSTANCES", DefaultRoles.ADMIN.getRolename()); final AdminConsoleFeatures MANAGE_REPLICATION = new AdminConsoleFeatures("MANAGE_REPLICATION", DefaultRoles.ADMIN.getRolename()); final AdminConsoleFeatures MANAGE_MASTERDATA_IMPORT = new AdminConsoleFeatures("MANAGE_MASTERDATA_IMPORT", DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures("MANAGE_DEVICE_CONFIGURATION", UserRoles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures("MANAGE_DEVICE_CONFIGURATION", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); final AdminConsoleFeatures MANAGE_USERS = new AdminConsoleFeatures("MANAGE_USERS", DefaultRoles.ADMIN.getRolename()); final AdminConsoleFeatures MANAGE_FILE_STORAGE = new AdminConsoleFeatures("MANAGE_FILE_STORAGE", DefaultRoles.ADMIN.getRolename()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java index 45f8f4a550b..47146f59fdd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java @@ -37,7 +37,7 @@ import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; -import com.sap.sailing.gwt.ui.usermanagement.UserRoles; +import com.sap.sailing.gwt.ui.server.security.Roles; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.ErrorReporter; @@ -480,7 +480,7 @@ public class MediaPlayerManagerComponent implements Component, PlayStateLi final VideoSynchPlayer videoPlayer; final UserDTO currentUser = userService.getCurrentUser(); boolean showSynchControls = currentUser != null && (currentUser.hasRole(DefaultRoles.ADMIN.getRolename()) || - currentUser.hasRole(UserRoles.eventmanager.getRolename()) || currentUser.hasRole(UserRoles.mediaeditor.getRolename())); + currentUser.hasRole(Roles.eventmanager.getRolename()) || currentUser.hasRole(Roles.mediaeditor.getRolename())); if (videoTrack.isYoutube()) { videoPlayer = new VideoYoutubePlayer(videoTrack, getRaceStartTime(), showSynchControls, raceTimer); } else { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java old mode 100755 new mode 100644 index 32f51644efb..11b2ce52aba --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java @@ -3,16 +3,20 @@ package com.sap.sailing.gwt.ui.server.security; import com.sap.sse.security.shared.Role; public enum Roles implements Role { - EVENT_MANAGER("event_manager"); - + spectator("spectator"), + moderator("moderator"), + sailor("sailor"), + coach("coach"), + eventmanager("eventmanager"), + mediaeditor("mediaeditor"); + private Roles(String rolename) { this.rolename = rolename; } - @Override public String getRolename() { return rolename; } - + private final String rolename; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java deleted file mode 100644 index ff5cc78c265..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/usermanagement/UserRoles.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.sap.sailing.gwt.ui.usermanagement; - -public enum UserRoles { - spectator("spectator"), - moderator("moderator"), - sailor("sailor"), - coach("coach"), - eventmanager("eventmanager"), - mediaeditor("mediaeditor"); - - private UserRoles(String rolename) { - this.rolename = rolename; - } - - public String getRolename() { - return rolename; - } - - private final String rolename; -} From b4c6dd1eff6062eaa435be2dfb8da7bf8c3be731 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 16:44:51 +0100 Subject: [PATCH 107/264] moved to the use of permissions instead of roles for the AdminConsole; however, this will currently disable the effects of role ADMIN --- .../META-INF/MANIFEST.MF | 3 +- .../adminconsole/AdminConsoleFeatures.java | 10 +- .../gwt/adminconsole/AdminConsolePanel.java | 14 +- .../security/shared/WildcardPermission.java | 252 ++++++++++++++++++ .../ui/client/component/UserDetailsView.java | 11 +- .../component/UserListDataProvider.java | 1 - .../client/component/UserManagementPanel.java | 6 +- .../ui/server/UserManagementServiceImpl.java | 6 +- .../sap/sse/security/ui/shared/UserDTO.java | 55 ++-- 9 files changed, 302 insertions(+), 56 deletions(-) create mode 100755 java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java diff --git a/java/com.sap.sse.gwt.adminconsole/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt.adminconsole/META-INF/MANIFEST.MF index 7bdbb366278..0fa8c2cf366 100644 --- a/java/com.sap.sse.gwt.adminconsole/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt.adminconsole/META-INF/MANIFEST.MF @@ -9,6 +9,7 @@ Require-Bundle: com.google.gwt.osgi;bundle-version="2.7.0", com.sap.sse.gwt, com.sap.sse.security.ui, com.sap.sse.security, - com.sap.sse.common + com.sap.sse.common, + com.sap.sse.security.common Export-Package: com.sap.sse.gwt.adminconsole Bundle-ActivationPolicy: lazy diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java index 9b57feda0e6..777a13c7c2e 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java @@ -11,17 +11,17 @@ import com.sap.sse.common.impl.NamedImpl; */ public class AdminConsoleFeatures extends NamedImpl { private static final long serialVersionUID = 7441608351911944793L; - private String[] enabledRoles; + private String requiredPermission; - public AdminConsoleFeatures(String name, String... enabledRoles) { + public AdminConsoleFeatures(String name, String requiredPermission) { super(name); - this.enabledRoles = enabledRoles; + this.requiredPermission = requiredPermission; } /** * If the user has at least one of these roles, he/she is permitted to use this feature. */ - public String[] getEnabledRoles() { - return enabledRoles; + public String getRequiredPermission() { + return requiredPermission; } } diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java index 5a069641414..b4a6c44845e 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java @@ -273,23 +273,11 @@ public class AdminConsolePanel extends DockLayoutPanel { for (Map.Entry, AdminConsoleFeatures> e : roleSpecificTabs .entrySet()) { final Widget panelToAdd = e.getKey().getB(); - if (user != null && isUserInRole(e.getValue().getEnabledRoles())) { + if (user != null && user.hasPermission(e.getValue().getRequiredPermission())) { e.getKey().getA().add(panelToAdd, e.getKey().getC(), /* asHtml */false); } else { e.getKey().getA().remove(panelToAdd); } } } - - private boolean isUserInRole(String... roles) { - boolean result = false; - UserDTO user = getUserService().getCurrentUser(); - for (String enabledRole : roles) { - if (user.hasRole(enabledRole)) { - result = true; - break; - } - } - return result; - } } diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java new file mode 100755 index 00000000000..ee88454a0e9 --- /dev/null +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.sap.sse.security.shared; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * A WildcardPermission is a very flexible permission construct supporting multiple levels of + * permission matching. However, most people will probably follow some standard conventions as explained below. + *

    + *

    Simple Usage

    + *

    + * In the simplest form, WildcardPermission can be used as a simple permission string. You could grant a + * user an "editNewsletter" permission and then check to see if the user has the editNewsletter + * permission by calling + *

    + * subject.isPermitted("editNewsletter") + *

    + * This is (mostly) equivalent to + *

    + * subject.isPermitted( new WildcardPermission("editNewsletter") ) + *

    + * but more on that later. + *

    + * The simple permission string may work for simple applications, but it requires you to have permissions like + * "viewNewsletter", "deleteNewsletter", + * "createNewsletter", etc. You can also grant a user "*" permissions + * using the wildcard character (giving this class its name), which means they have all permissions. But + * using this approach there's no way to just say a user has "all newsletter permissions". + *

    + * For this reason, WildcardPermission supports multiple levels of permissioning. + *

    + *

    Multiple Levels

    + *

    + * WildcardPermission also supports the concept of multiple levels. For example, you could + * restructure the previous simple example by granting a user the permission "newsletter:edit". + * The colon in this example is a special character used by the WildcardPermission that delimits the + * next token in the permission. + *

    + * In this example, the first token is the domain that is being operated on + * and the second token is the action being performed. Each level can contain multiple values. So you + * could simply grant a user the permission "newsletter:view,edit,create" which gives them + * access to perform view, edit, and create actions in the newsletter + * domain. Then you could check to see if the user has the "newsletter:create" + * permission by calling + *

    + * subject.isPermitted("newsletter:create") + *

    + * (which would return true). + *

    + * In addition to granting multiple permissions via a single string, you can grant all permission for a particular + * level. So if you wanted to grant a user all actions in the newsletter domain, you could simply give + * them "newsletter:*". Now, any permission check for "newsletter:XXX" + * will return true. It is also possible to use the wildcard token at the domain level (or both): so you + * could grant a user the "view" action across all domains "*:view". + *

    + *

    Instance-level Access Control

    + *

    + * Another common usage of the WildcardPermission is to model instance-level Access Control Lists. + * In this scenario you use three tokens - the first is the domain, the second is the action, and + * the third is the instance you are acting on. + *

    + * So for example you could grant a user "newsletter:edit:12,13,18". In this example, assume + * that the third token is the system's ID of the newsletter. That would allow the user to edit newsletters + * 12, 13, and 18. This is an extremely powerful way to express permissions, + * since you can now say things like "newsletter:*:13" (grant a user all actions for newsletter + * 13), "newsletter:view,create,edit:*" (allow the user to + * view, create, or edit any newsletter), or + * "newsletter:*:* (allow the user to perform any action on any newsletter). + *

    + * To perform checks against these instance-level permissions, the application should include the instance ID in the + * permission check like so: + *

    + * subject.isPermitted( "newsletter:edit:13" ) + *

    + * There is no limit to the number of tokens that can be used, so it is up to your imagination in terms of ways that + * this could be used in your application. However, the Shiro team likes to standardize some common usages shown + * above to help people get started and provide consistency in the Shiro community.

    + * + * This class has been slightly adjusted from the original Shiro version such that it no longer depends on any Shiro + * classes but still offers the same services that the Shiro WildcardPermission does. This way, the + * permission can also be used by a GWT client and an Android implementation. + */ +public class WildcardPermission implements Serializable { + + //TODO - JavaDoc methods + + /** + * + */ + private static final long serialVersionUID = -7136806951296823464L; + /*-------------------------------------------- + | C O N S T A N T S | + ============================================*/ + protected static final String WILDCARD_TOKEN = "*"; + protected static final String PART_DIVIDER_TOKEN = ":"; + protected static final String SUBPART_DIVIDER_TOKEN = ","; + protected static final boolean DEFAULT_CASE_SENSITIVE = false; + + /*-------------------------------------------- + | I N S T A N C E V A R I A B L E S | + ============================================*/ + private List> parts; + + /*-------------------------------------------- + | C O N S T R U C T O R S | + ============================================*/ + /** + * Default no-arg constructor for subclasses only - end-user developers instantiating Permission instances must + * provide a wildcard string at a minimum, since Permission instances are immutable once instantiated. + *

    + * Note that the WildcardPermission class is very robust and typically subclasses are not necessary unless you + * wish to create type-safe Permission objects that would be used in your application, such as perhaps a + * {@code UserPermission}, {@code SystemPermission}, {@code PrinterPermission}, etc. If you want such type-safe + * permission usage, consider subclassing the {@link DomainPermission DomainPermission} class for your needs. + */ + protected WildcardPermission() { + } + + public WildcardPermission(String wildcardString) { + this(wildcardString, DEFAULT_CASE_SENSITIVE); + } + + public WildcardPermission(String wildcardString, boolean caseSensitive) { + setParts(wildcardString, caseSensitive); + } + + protected void setParts(String wildcardString) { + setParts(wildcardString, DEFAULT_CASE_SENSITIVE); + } + + protected void setParts(String wildcardString, boolean caseSensitive) { + if (wildcardString == null || wildcardString.trim().length() == 0) { + throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted."); + } + + wildcardString = wildcardString.trim(); + + List parts = Arrays.asList(wildcardString.split(PART_DIVIDER_TOKEN)); + + this.parts = new ArrayList>(); + for (String part : parts) { + Set subparts = new HashSet<>(Arrays.asList(part.split(SUBPART_DIVIDER_TOKEN))); + if (!caseSensitive) { + subparts = lowercase(subparts); + } + if (subparts.isEmpty()) { + throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted."); + } + this.parts.add(subparts); + } + + if (this.parts.isEmpty()) { + throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted."); + } + } + + private Set lowercase(Set subparts) { + Set lowerCasedSubparts = new LinkedHashSet(subparts.size()); + for (String subpart : subparts) { + lowerCasedSubparts.add(subpart.toLowerCase()); + } + return lowerCasedSubparts; + } + + /*-------------------------------------------- + | A C C E S S O R S / M O D I F I E R S | + ============================================*/ + protected List> getParts() { + return this.parts; + } + + /*-------------------------------------------- + | M E T H O D S | + ============================================*/ + + public boolean implies(WildcardPermission wp) { + // By default only supports comparisons with other WildcardPermissions + List> otherParts = wp.getParts(); + + int i = 0; + for (Set otherPart : otherParts) { + // If this permission has less parts than the other permission, everything after the number of parts contained + // in this permission is automatically implied, so return true + if (getParts().size() - 1 < i) { + return true; + } else { + Set part = getParts().get(i); + if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) { + return false; + } + i++; + } + } + + // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards + for (; i < getParts().size(); i++) { + Set part = getParts().get(i); + if (!part.contains(WILDCARD_TOKEN)) { + return false; + } + } + + return true; + } + + public String toString() { + StringBuilder buffer = new StringBuilder(); + for (Set part : parts) { + if (buffer.length() > 0) { + buffer.append(":"); + } + buffer.append(part); + } + return buffer.toString(); + } + + public boolean equals(Object o) { + if (o instanceof WildcardPermission) { + WildcardPermission wp = (WildcardPermission) o; + return parts.equals(wp.parts); + } + return false; + } + + public int hashCode() { + return parts.hashCode(); + } + +} diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java index 6ee94c0fe1e..45331f2ca45 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java @@ -53,7 +53,7 @@ public class UserDetailsView extends FlowPanel { private final VerticalPanel accountPanels; private UserDTO user; - public UserDetailsView(final UserService userService, UserDTO user, final StringMessages stringMessages) { + public UserDetailsView(final UserService userService, UserDTO user, final StringMessages stringMessages, final UserListDataProvider userListDataProvider) { final UserManagementServiceAsync userManagementService = userService.getUserManagementService(); this.stringMessages = stringMessages; this.user = user; @@ -85,7 +85,7 @@ public class UserDetailsView extends FlowPanel { if (!result.isSuccessful()) { Window.alert(stringMessages.errorUpdatingRoles(selectedUser.getName(), result.getMessage())); } else { - selectedUser.setRoles(newRoleList); + userListDataProvider.updateDisplays(); if (userService.getCurrentUser().getName().equals(selectedUser.getName())) { // if the current user's roles changed, update the user object in the user service and notify others userService.updateUser(/* notify other instances */ true); @@ -95,7 +95,8 @@ public class UserDetailsView extends FlowPanel { })); } }); - permissionsEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getPermissions(), stringMessages, com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), defaultPermissionNames, + permissionsEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getStringPermissions(), stringMessages, + com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), defaultPermissionNames, stringMessages.enterPermissionName()); permissionsEditor.addValueChangeHandler(new ValueChangeHandler>() { @Override @@ -114,7 +115,7 @@ public class UserDetailsView extends FlowPanel { if (!result.isSuccessful()) { Window.alert(stringMessages.errorUpdatingPermissions(selectedUser.getName(), result.getMessage())); } else { - selectedUser.setPermissions(newPermissionList); + userListDataProvider.updateDisplays(); if (userService.getCurrentUser().getName().equals(selectedUser.getName())) { // if the current user's permissions changed, update the user object in the user service and notify others userService.updateUser(/* notify other instances */ true); @@ -240,7 +241,7 @@ public class UserDetailsView extends FlowPanel { accountPanels.add(accountPanelDecorator); } rolesEditor.setValue(user.getRoles(), /* fireEvents */ false); - permissionsEditor.setValue(user.getPermissions(), /* fireEvents */ false); + permissionsEditor.setValue(user.getStringPermissions(), /* fireEvents */ false); } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java index e9d7b37c1ab..1b610f25013 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserListDataProvider.java @@ -76,6 +76,5 @@ public class UserListDataProvider extends AbstractDataProvider { for (HasData hd : getDataDisplays()) { onRangeChanged(hd); } -// //TODO [D056866] SelectionChangeEvent.fire(singleSelectionModel); } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java index ec1f706e1bc..ae04b102da6 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java @@ -80,7 +80,9 @@ public class UserManagementPanel extends DockPanel { deleteButton.setEnabled(singleSelectionModel.getSelectedObject() != null); final UserList userList = new UserList(); userList.setSelectionModel(singleSelectionModel); - final UserDetailsView userDetailsView = new UserDetailsView(userService, singleSelectionModel.getSelectedObject(), stringMessages); + TextBox filterBox = new TextBox(); + userListDataProvider = new UserListDataProvider(userManagementService, filterBox); + final UserDetailsView userDetailsView = new UserDetailsView(userService, singleSelectionModel.getSelectedObject(), stringMessages, userListDataProvider); add(userDetailsView, DockPanel.CENTER); userDetailsView.addUserChangeEventHandler(new UserChangeEventHandler() { @Override @@ -96,8 +98,6 @@ public class UserManagementPanel extends DockPanel { } }); userList.setPageSize(20); - TextBox filterBox = new TextBox(); - userListDataProvider = new UserListDataProvider(userManagementService, filterBox); userListDataProvider.addDataDisplay(userList); SimplePager pager = new SimplePager(TextLocation.CENTER, false, /* fast forward step size */ 50, true); pager.setDisplay(userList); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index a04ae8a236d..a82e319f250 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -272,7 +272,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U } } - private UserDTO createUserDTOFromUser(User user){ + private UserDTO createUserDTOFromUser(User user) { UserDTO userDTO; Map accounts = user.getAllAccounts(); List accountDTOs = new ArrayList<>(); @@ -288,9 +288,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U break; } } - userDTO = new UserDTO(user.getName(), user.getEmail(), user.isEmailValidated(), accountDTOs); - userDTO.addRoles(user.getRoles()); - userDTO.addPermissions(user.getPermissions()); + userDTO = new UserDTO(user.getName(), user.getEmail(), user.isEmailValidated(), accountDTOs, user.getRoles(), user.getPermissions()); return userDTO; } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java index de53f0b7ffd..07ed428082f 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java @@ -1,27 +1,35 @@ package com.sap.sse.security.ui.shared; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import com.google.gwt.user.client.rpc.IsSerializable; import com.sap.sse.common.Util; +import com.sap.sse.security.shared.WildcardPermission; public class UserDTO implements IsSerializable { private String name; private String email; private List accounts; - private Set roles = new HashSet<>(); - private Set permissions = new HashSet<>(); + private Set roles; + private Set permissions; private boolean emailValidated; UserDTO() {} // for serialization only - public UserDTO(String name, String email, boolean emailValidated, List accounts) { + public UserDTO(String name, String email, boolean emailValidated, List accounts, Iterable roles, Iterable stringPermissions) { this.name = name; this.email = email; this.emailValidated = emailValidated; this.accounts = accounts; + this.roles = new HashSet<>(); + Util.addAll(roles, this.roles); + this.permissions = new HashSet<>(); + for (String stringPermission : stringPermissions) { + this.permissions.add(new WildcardPermission(stringPermission)); + } } public String getName() { @@ -36,32 +44,31 @@ public class UserDTO implements IsSerializable { return roles.contains(role); } - public void addRoles(Iterable roles) { - Util.addAll(roles, this.roles); - } - - public void setRoles(Iterable newRoleList) { - this.roles.clear(); - Util.addAll(newRoleList, this.roles); - } - - public Iterable getPermissions() { + public Iterable getPermissions() { return permissions; } - public boolean hasPermission(String permission) { - return permissions.contains(permission); - } - - public void addPermissions(Iterable permissions) { - Util.addAll(permissions, this.permissions); - } - - public void setPermissions(Iterable newPermissionList) { - this.permissions.clear(); - Util.addAll(newPermissionList, this.permissions); + public Iterable getStringPermissions() { + List result = new ArrayList<>(); + for (WildcardPermission wp : getPermissions()) { + result.add(wp.toString()); + } + return result; } + public boolean hasPermission(String permission) { + return hasPermission(new WildcardPermission(permission)); + } + + public boolean hasPermission(WildcardPermission permission) { + for (WildcardPermission p : permissions) { + if (p.implies(permission)) { + return true; + } + } + return false; + } + public List getAccounts() { return accounts; } From 3511857662a37706bb9e67b96942d5f1067a6a30 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 16:54:50 +0100 Subject: [PATCH 108/264] made compileable again by fixing AdminConsoleFeatures and Permissions; probably could eliminate AdminConsoleFeatures as it now matches up with Permissions --- .../SailingAdminConsoleFeatures.java | 43 +++++++++---------- .../gwt/ui/server/security/Permission.java | 29 +++++++++++++ .../gwt/ui/server/security/Permissions.java | 5 --- .../adminconsole/AdminConsoleFeatures.java | 7 +-- .../gwt/adminconsole/AdminConsolePanel.java | 2 +- .../security/shared/DefaultPermissions.java | 12 +++--- .../sap/sse/security/shared/Permission.java | 9 ++++ .../ui/client/component/UserDetailsView.java | 2 +- 8 files changed, 71 insertions(+), 38 deletions(-) create mode 100755 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permission.java delete mode 100755 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permissions.java create mode 100755 java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Permission.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java index b7b64309796..bad4ee7ab36 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java @@ -1,28 +1,27 @@ package com.sap.sailing.gwt.ui.adminconsole; -import com.sap.sailing.gwt.ui.server.security.Roles; +import com.sap.sailing.gwt.ui.server.security.Permission; import com.sap.sse.gwt.adminconsole.AdminConsoleFeatures; -import com.sap.sse.security.shared.DefaultRoles; public interface SailingAdminConsoleFeatures { - final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures("MANAGE_EVENTS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures("MANAGE_REGATTAS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures("MANAGE_TRACKED_RACES", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures("SHOW_TRACKED_RACES", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_RACELOG_TRACKING = new AdminConsoleFeatures("MANAGE_RACELOG_TRACKING", DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_ALL_COMPETITORS = new AdminConsoleFeatures("MANAGE_ALL_COMPETITORS", DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures("MANAGE_COURSE_LAYOUT", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures("MANAGE_WIND", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures("MANAGE_IGTIMI_ACCOUNTS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures("MANAGE_LEADERBOARDS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures("MANAGE_LEADERBOARD_GROUPS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_RESULT_IMPORT_URLS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_STRUCTURE_IMPORT_URLS", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures("MANAGE_MEDIA", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_SAILING_SERVER_INSTANCES = new AdminConsoleFeatures("MANAGE_SAILING_SERVER_INSTANCES", DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_REPLICATION = new AdminConsoleFeatures("MANAGE_REPLICATION", DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_MASTERDATA_IMPORT = new AdminConsoleFeatures("MANAGE_MASTERDATA_IMPORT", DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures("MANAGE_DEVICE_CONFIGURATION", Roles.eventmanager.getRolename(), DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_USERS = new AdminConsoleFeatures("MANAGE_USERS", DefaultRoles.ADMIN.getRolename()); - final AdminConsoleFeatures MANAGE_FILE_STORAGE = new AdminConsoleFeatures("MANAGE_FILE_STORAGE", DefaultRoles.ADMIN.getRolename()); + final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures("MANAGE_EVENTS", Permission.MANAGE_EVENTS); + final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures("MANAGE_REGATTAS", Permission.MANAGE_REGATTAS); + final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures("MANAGE_TRACKED_RACES", Permission.MANAGE_TRACKED_RACES); + final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures("SHOW_TRACKED_RACES", Permission.SHOW_TRACKED_RACES); + final AdminConsoleFeatures MANAGE_RACELOG_TRACKING = new AdminConsoleFeatures("MANAGE_RACELOG_TRACKING", Permission.MANAGE_RACELOG_TRACKING); + final AdminConsoleFeatures MANAGE_ALL_COMPETITORS = new AdminConsoleFeatures("MANAGE_ALL_COMPETITORS", Permission.MANAGE_ALL_COMPETITORS); + final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures("MANAGE_COURSE_LAYOUT", Permission.MANAGE_COURSE_LAYOUT); + final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures("MANAGE_WIND", Permission.MANAGE_WIND); + final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures("MANAGE_IGTIMI_ACCOUNTS", Permission.MANAGE_IGTIMI_ACCOUNTS); + final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures("MANAGE_LEADERBOARDS", Permission.MANAGE_LEADERBOARDS); + final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures("MANAGE_LEADERBOARD_GROUPS", Permission.MANAGE_LEADERBOARD_GROUPS); + final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_RESULT_IMPORT_URLS", Permission.MANAGE_RESULT_IMPORT_URLS); + final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_STRUCTURE_IMPORT_URLS", Permission.MANAGE_STRUCTURE_IMPORT_URLS); + final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures("MANAGE_MEDIA", Permission.MANAGE_MEDIA); + final AdminConsoleFeatures MANAGE_SAILING_SERVER_INSTANCES = new AdminConsoleFeatures("MANAGE_SAILING_SERVER_INSTANCES", Permission.MANAGE_SAILING_SERVER_INSTANCES); + final AdminConsoleFeatures MANAGE_REPLICATION = new AdminConsoleFeatures("MANAGE_REPLICATION", Permission.MANAGE_REPLICATION); + final AdminConsoleFeatures MANAGE_MASTERDATA_IMPORT = new AdminConsoleFeatures("MANAGE_MASTERDATA_IMPORT", Permission.MANAGE_MASTERDATA_IMPORT); + final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures("MANAGE_DEVICE_CONFIGURATION", Permission.MANAGE_DEVICE_CONFIGURATION); + final AdminConsoleFeatures MANAGE_USERS = new AdminConsoleFeatures("MANAGE_USERS", Permission.MANAGE_USERS); + final AdminConsoleFeatures MANAGE_FILE_STORAGE = new AdminConsoleFeatures("MANAGE_FILE_STORAGE", Permission.MANAGE_FILE_STORAGE); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permission.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permission.java new file mode 100755 index 00000000000..63e0bc681e2 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permission.java @@ -0,0 +1,29 @@ +package com.sap.sailing.gwt.ui.server.security; + +public enum Permission implements com.sap.sse.security.shared.Permission { + MANAGE_EVENTS, + MANAGE_REGATTAS, + MANAGE_TRACKED_RACES, + SHOW_TRACKED_RACES, + MANAGE_RACELOG_TRACKING, + MANAGE_ALL_COMPETITORS, + MANAGE_COURSE_LAYOUT, + MANAGE_WIND, + MANAGE_IGTIMI_ACCOUNTS, + MANAGE_LEADERBOARDS, + MANAGE_LEADERBOARD_GROUPS, + MANAGE_RESULT_IMPORT_URLS, + MANAGE_STRUCTURE_IMPORT_URLS, + MANAGE_MEDIA, + MANAGE_SAILING_SERVER_INSTANCES, + MANAGE_REPLICATION, + MANAGE_MASTERDATA_IMPORT, + MANAGE_DEVICE_CONFIGURATION, + MANAGE_USERS, + MANAGE_FILE_STORAGE; + + @Override + public String getStringPermission() { + return name()+":"; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permissions.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permissions.java deleted file mode 100755 index ac4a997b04e..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permissions.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.sap.sailing.gwt.ui.server.security; - -public enum Permissions { - -} diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java index 777a13c7c2e..18800735b6a 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java @@ -1,6 +1,7 @@ package com.sap.sse.gwt.adminconsole; import com.sap.sse.common.impl.NamedImpl; +import com.sap.sse.security.shared.Permission; /** * Specifies a feature with a name and the role names, one of which is required in order to be allowed @@ -11,9 +12,9 @@ import com.sap.sse.common.impl.NamedImpl; */ public class AdminConsoleFeatures extends NamedImpl { private static final long serialVersionUID = 7441608351911944793L; - private String requiredPermission; + private Permission requiredPermission; - public AdminConsoleFeatures(String name, String requiredPermission) { + public AdminConsoleFeatures(String name, Permission requiredPermission) { super(name); this.requiredPermission = requiredPermission; } @@ -21,7 +22,7 @@ public class AdminConsoleFeatures extends NamedImpl { /** * If the user has at least one of these roles, he/she is permitted to use this feature. */ - public String getRequiredPermission() { + public Permission getRequiredPermission() { return requiredPermission; } } diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java index b4a6c44845e..b9abe0018cd 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java @@ -273,7 +273,7 @@ public class AdminConsolePanel extends DockLayoutPanel { for (Map.Entry, AdminConsoleFeatures> e : roleSpecificTabs .entrySet()) { final Widget panelToAdd = e.getKey().getB(); - if (user != null && user.hasPermission(e.getValue().getRequiredPermission())) { + if (user != null && user.hasPermission(e.getValue().getRequiredPermission().getStringPermission())) { e.getKey().getA().add(panelToAdd, e.getKey().getC(), /* asHtml */false); } else { e.getKey().getA().remove(panelToAdd); diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultPermissions.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultPermissions.java index 79b1928472e..dbc47ea78c8 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultPermissions.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/DefaultPermissions.java @@ -1,15 +1,15 @@ package com.sap.sse.security.shared; -public enum DefaultPermissions { +public enum DefaultPermissions implements Permission { ALL_EVENTS("event:*"); - private DefaultPermissions(String permissionname) { - this.permissionname = permissionname; + private DefaultPermissions(String stringPermission) { + this.stringPermission = stringPermission; } - public String getPermissionname() { - return permissionname; + public String getStringPermission() { + return stringPermission; } - private final String permissionname; + private final String stringPermission; } diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Permission.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Permission.java new file mode 100755 index 00000000000..d2f9695650b --- /dev/null +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/Permission.java @@ -0,0 +1,9 @@ +package com.sap.sse.security.shared; + +public interface Permission { + String name(); + + int ordinal(); + + String getStringPermission(); +} diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java index 45331f2ca45..b7144c87f7a 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java @@ -64,7 +64,7 @@ public class UserDetailsView extends FlowPanel { } List defaultPermissionNames = new ArrayList<>(); for (DefaultPermissions defaultPermission : DefaultPermissions.values()) { - defaultPermissionNames.add(defaultPermission.getPermissionname()); + defaultPermissionNames.add(defaultPermission.getStringPermission()); } rolesEditor = new StringListEditorComposite(user==null?Collections.emptySet():user.getRoles(), stringMessages, com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), defaultRoleNames, stringMessages.enterRoleName()); From b4b646be1fdcd873022e20ed5ded7ec401ff1d3f Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Fri, 13 Feb 2015 17:59:57 +0100 Subject: [PATCH 109/264] refactor AbstractServerReplicationTest to enable tests with multiple replicable services in future --- .../test/AbstractLogReplicationTest.java | 3 +- .../test/AbstractServerReplicationTest.java | 59 ++++++++------- .../test/ConnectionResetAndReconnectTest.java | 26 ++++--- ...LeaderboardCorrectionsReplicationTest.java | 3 +- ...tialLoadReplicationObjectIdentityTest.java | 11 +-- .../test/PrematureOperationReceiptTest.java | 3 +- .../test/RaceLogReplicationTest.java | 5 +- .../test/RegattaLogReplicationTest.java | 3 +- ...orageManagementServiceReplicationTest.java | 72 ++++++++++--------- ...> AbstractServerReplicationTestSetUp.java} | 53 ++++++++++---- ...erWithMultipleServicesReplicationTest.java | 25 +++++++ ...erverWithSingleServiceReplicationTest.java | 63 ++++++++++++++++ .../META-INF/MANIFEST.MF | 2 +- .../test/AbstractSecurityReplicationTest.java | 46 ++++++------ 14 files changed, 260 insertions(+), 114 deletions(-) rename java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/{AbstractServerReplicationTest.java => AbstractServerReplicationTestSetUp.java} (92%) mode change 100755 => 100644 create mode 100644 java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java create mode 100755 java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java index 44d0a431ec4..5d9524cd142 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java @@ -35,8 +35,9 @@ public abstract class AbstractLogReplicationTest { - protected MongoDBService mongoDBService; - protected MongoObjectFactory mongoObjectFactory; +public abstract class AbstractServerReplicationTest extends com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest { + protected ServerReplicationTestSetUp testSetUp; - /** - * Drops the test DB, if dropDB is true and requests the DB to start. - */ - @Override - protected void persistenceSetUp(boolean dropDB) { - mongoDBService = MongoDBService.INSTANCE; - if (dropDB) { - mongoDBService.getDB().dropDatabase(); - } - mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService); + public AbstractServerReplicationTest() { + super(new ServerReplicationTestSetUp()); + testSetUp = (ServerReplicationTestSetUp) super.testSetUp; } - protected RacingEventServiceImpl createNewMaster() { - return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, - DomainFactory.INSTANCE), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), - EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); - } - - protected RacingEventServiceImpl createNewReplica() { - return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, - // replica gets its own base DomainFactory: - new DomainFactoryImpl()), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), - EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + protected static class ServerReplicationTestSetUp extends com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp { + protected MongoDBService mongoDBService; + protected MongoObjectFactory mongoObjectFactory; + + /** + * Drops the test DB, if dropDB is true and requests the DB to start. + */ + @Override + protected void persistenceSetUp(boolean dropDB) { + mongoDBService = MongoDBService.INSTANCE; + if (dropDB) { + mongoDBService.getDB().dropDatabase(); + } + mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService); + } + + @Override + public RacingEventServiceImpl createNewMaster() { + return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, + DomainFactory.INSTANCE), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), + EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + } + + @Override + public RacingEventServiceImpl createNewReplica() { + return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, + // replica gets its own base DomainFactory: + new DomainFactoryImpl()), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), + EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + } } } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java index 30ce408fa99..65da3647ea9 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java @@ -81,16 +81,20 @@ public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTe } - @Before - @Override - public void setUp() throws Exception { - try { - Pair, ReplicationMasterDescriptor> result = basicSetUp(/* dropDB */ true, - /* master=null means create a new one */ null, /* replica=null means create a new one */null); - masterDescriptor = MasterReplicationDescriptorMock.from(result.getB()); - } catch (Exception e) { - e.printStackTrace(); - tearDown(); + protected static class ServerReplicationTestSetUp extends + com.sap.sailing.server.replication.test.AbstractServerReplicationTest.ServerReplicationTestSetUp { + @Before + @Override + public void setUp() throws Exception { + try { + Pair, ReplicationMasterDescriptor> result = basicSetUp( + /* dropDB */true, + /* master=null means create a new one */null, /* replica=null means create a new one */null); + masterDescriptor = MasterReplicationDescriptorMock.from(result.getB()); + } catch (Exception e) { + e.printStackTrace(); + tearDown(); + } } } @@ -102,7 +106,7 @@ public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTe /* until here both instances should have the same in-memory state. * now lets add an event on master and stop the messaging queue. */ stopMessagingExchange(); - replicaReplicator.startToReplicateFrom(masterDescriptor); + testSetUp.getReplicaReplicator().startToReplicateFrom(testSetUp.getMasterDescriptor()); Event event = addEventOnMaster(); Thread.sleep(1000); // wait for master queue to get filled assertNull(replica.getEvent(event.getId())); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java index c28f2ea50e6..9bb2e1db068 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java @@ -46,6 +46,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.mongodb.MongoDBService; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.ReplicationService; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class DelayedLeaderboardCorrectionsReplicationTest extends AbstractServerReplicationTest { private static final Logger logger = Logger.getLogger(DelayedLeaderboardCorrectionsReplicationTest.class.getName()); @@ -115,7 +116,7 @@ public class DelayedLeaderboardCorrectionsReplicationTest extends AbstractServer masterLeaderboardReloaded.getRaceColumnByName(Q2), MillisecondsTimePoint.now())); // replicate the re-loaded environment - Pair, ReplicationMasterDescriptor> descriptors = basicSetUp( + Pair, ReplicationMasterDescriptor> descriptors = basicSetUp( /* dropDB */false, master, replica); replicaReplicator = descriptors.getA(); masterDescriptor = descriptors.getB(); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java index 862220bc514..a2b082373bd 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java @@ -73,9 +73,10 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.impl.ReplicationReceiver; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class InitialLoadReplicationObjectIdentityTest extends AbstractServerReplicationTest { - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; + private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; /** * Drops the test DB. Sets up master and replica, starts the JMS message broker and registers the replica with the master. @@ -84,10 +85,10 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl @Override public void setUp() throws Exception { persistenceSetUp(/* dropDB */ true); - this.master = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE - .getMongoObjectFactory(mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); - this.replica = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE - .getMongoObjectFactory(mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + this.master = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(testSetUp.mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE + .getMongoObjectFactory(testSetUp.mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(testSetUp.mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + this.replica = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(testSetUp.mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE + .getMongoObjectFactory(testSetUp.mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(testSetUp.mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); } private void performReplicationSetup() throws Exception { diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java index 6e640221b30..325e88864d0 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java @@ -15,6 +15,7 @@ import com.sap.sailing.server.operationaltransformation.CreateFlexibleLeaderboar import com.sap.sse.common.Util.Pair; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.impl.ReplicationReceiver; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class PrematureOperationReceiptTest extends AbstractServerReplicationTest { private ReplicationReceiver replicator; @@ -25,7 +26,7 @@ public class PrematureOperationReceiptTest extends AbstractServerReplicationTest @Before @Override public void setUp() throws Exception { - Pair, ReplicationMasterDescriptor> result = + Pair, ReplicationMasterDescriptor> result = basicSetUp(/* dropDB */ true, /* master=null means create a new one */ null, /* replica=null means create a new one */ null); replicator = replicaReplicator.startToReplicateFromButDontYetFetchInitialLoad(result.getB(), /* startReplicatorSuspended */ true); } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java index 9660d8b92f3..791aaa82e29 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java @@ -47,10 +47,11 @@ import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.replication.ReplicationMasterDescriptor; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class RaceLogReplicationTest extends AbstractLogReplicationTest { - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; + private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; private RaceLogEvent raceLogEvent; private RaceLogEvent anotherRaceLogEvent; @@ -217,7 +218,7 @@ public class RaceLogReplicationTest extends AbstractLogReplicationTest { - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; + private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; private RegattaLogEvent regattaLogEvent; // private RegattaLogEvent anotherRegattaLogEvent; diff --git a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java index be1aba6fc9a..b8e02563ed2 100755 --- a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java +++ b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java @@ -10,43 +10,49 @@ import com.sap.sse.filestorage.impl.EmptyFileStorageServicePropertyStoreImpl; import com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl; import com.sap.sse.filestorage.testsupport.DummyFileStorageService; import com.sap.sse.mongodb.MongoDBService; -import com.sap.sse.replication.testsupport.AbstractServerReplicationTest; +import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; public abstract class AbstractFileStorageManagementServiceReplicationTest extends - AbstractServerReplicationTest { - private MongoDBService mongoDBService; + AbstractServerWithSingleServiceReplicationTest { + public AbstractFileStorageManagementServiceReplicationTest() { + super(new ServerReplicationTestSetUp()); + } - @Override - protected void persistenceSetUp(boolean dropDB) { - mongoDBService = MongoDBService.INSTANCE; - if (dropDB) { + private static class ServerReplicationTestSetUp extends com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp { + private MongoDBService mongoDBService; + + @Override + protected void persistenceSetUp(boolean dropDB) { + mongoDBService = MongoDBService.INSTANCE; + if (dropDB) { + mongoDBService.getDB().dropDatabase(); + } + } + + protected FileStorageManagementServiceImpl createNewService() { + return new FileStorageManagementServiceImpl( + new SingleTypeBasedServiceFinderImpl<>(new DummyFileStorageService(), + DummyFileStorageService.NAME), EmptyFileStorageServicePropertyStoreImpl.INSTANCE); + } + + @Override + public FileStorageManagementServiceImpl createNewMaster() throws MalformedURLException, IOException, + InterruptedException { + FileStorageManagementServiceImpl result = createNewService(); + FileStorageService serviceOnMaster = result.getAvailableFileStorageServices()[0]; + result.setActiveFileStorageService(serviceOnMaster); + result.setFileStorageServiceProperty(serviceOnMaster, DummyFileStorageService.PROPERTY_NAME, "123"); + return result; + } + + @Override + public FileStorageManagementServiceImpl createNewReplica() { + return createNewService(); + } + + @Override + protected void persistenceTearDown() { mongoDBService.getDB().dropDatabase(); } } - - protected FileStorageManagementServiceImpl createNewService() { - return new FileStorageManagementServiceImpl( - new SingleTypeBasedServiceFinderImpl<>(new DummyFileStorageService(), - DummyFileStorageService.NAME), EmptyFileStorageServicePropertyStoreImpl.INSTANCE); - } - - @Override - protected FileStorageManagementServiceImpl createNewMaster() throws MalformedURLException, IOException, - InterruptedException { - FileStorageManagementServiceImpl result = createNewService(); - FileStorageService serviceOnMaster = result.getAvailableFileStorageServices()[0]; - result.setActiveFileStorageService(serviceOnMaster); - result.setFileStorageServiceProperty(serviceOnMaster, DummyFileStorageService.PROPERTY_NAME, "123"); - return result; - } - - @Override - protected FileStorageManagementServiceImpl createNewReplica() { - return createNewService(); - } - - @Override - protected void persistenceTearDown() { - mongoDBService.getDB().dropDatabase(); - } } diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTest.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java old mode 100755 new mode 100644 similarity index 92% rename from java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTest.java rename to java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java index 001315e97a8..2145e775726 --- a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTest.java +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java @@ -22,8 +22,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPOutputStream; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.rules.Timeout; @@ -42,10 +40,12 @@ import com.sap.sse.replication.impl.ReplicationReceiver; import com.sap.sse.replication.impl.ReplicationServiceImpl; import com.sap.sse.replication.impl.SingletonReplicablesProvider; -public abstract class AbstractServerReplicationTest, ReplicableImpl extends ReplicableInterface> { - private static final Logger logger = Logger.getLogger(AbstractServerReplicationTest.class.getName()); +public abstract class AbstractServerReplicationTestSetUp, ReplicableImpl extends ReplicableInterface> { + private static final Logger logger = Logger.getLogger(AbstractServerReplicationTestSetUp.class.getName()); - protected static final int SERVLET_PORT = 9990; + protected static final int DEFAULT_SERVLET_PORT = 9990; + + private final int servletPort; protected ReplicableImpl replica; protected ReplicableImpl master; protected ReplicationServiceTestImpl replicaReplicator; @@ -53,6 +53,14 @@ public abstract class AbstractServerReplicationTest, ReplicationMasterDescriptor> result = basicSetUp( @@ -120,9 +127,9 @@ public abstract class AbstractServerReplicationTest replicaReplicator = new ReplicationServiceTestImpl(exchangeName, exchangeHost, rim, replicaDescriptor, - this.replica, this.master, masterReplicator, masterDescriptor); + this.replica, this.master, masterReplicator, masterDescriptor, servletPort); Pair, ReplicationMasterDescriptor> result = new Pair<>(replicaReplicator, masterDescriptor); logger.info("starting initial load transmission servlet for "+getClass().getName()); initialLoadTestServerThread = replicaReplicator.startInitialLoadTransmissionServlet(); @@ -136,16 +143,15 @@ public abstract class AbstractServerReplicationTest getReplicaReplicator() { + return replicaReplicator; + } + + public ReplicationMasterDescriptor getMasterDescriptor() { + return masterDescriptor; + } } diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java new file mode 100644 index 00000000000..125c97f6dee --- /dev/null +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java @@ -0,0 +1,25 @@ +package com.sap.sse.replication.testsupport; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; + +public abstract class AbstractServerWithMultipleServicesReplicationTest { + protected final Set> testSetUps = new HashSet<>(); + + @Before + public void setUp() throws Exception { + for (AbstractServerReplicationTestSetUp testSetUp : testSetUps) { + testSetUp.setUp(); + } + } + + @After + public void tearDown() throws Exception { + for (AbstractServerReplicationTestSetUp testSetUp : testSetUps) { + testSetUp.tearDown(); + } + } +} diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java new file mode 100755 index 00000000000..2e20ed3ee1c --- /dev/null +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java @@ -0,0 +1,63 @@ +package com.sap.sse.replication.testsupport; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; + +import com.sap.sse.common.Util.Pair; +import com.sap.sse.replication.Replicable; +import com.sap.sse.replication.ReplicationMasterDescriptor; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; + +public abstract class AbstractServerWithSingleServiceReplicationTest, ReplicableImpl extends ReplicableInterface> { + protected final AbstractServerReplicationTestSetUp testSetUp; + protected ReplicableImpl replica; + protected ReplicableImpl master; + protected ReplicationServiceTestImpl replicaReplicator; + protected ReplicationMasterDescriptor masterDescriptor; + + + public AbstractServerWithSingleServiceReplicationTest(AbstractServerReplicationTestSetUp testSetUp) { + this.testSetUp = testSetUp; + } + + @Before + public void setUp() throws Exception { + testSetUp.setUp(); + master = testSetUp.getMaster(); + replica = testSetUp.getReplica(); + replicaReplicator = testSetUp.getReplicaReplicator(); + masterDescriptor = testSetUp.getMasterDescriptor(); + } + + protected Pair, ReplicationMasterDescriptor> basicSetUp(boolean dropDB, + ReplicableImpl master, ReplicableImpl replica) throws Exception { + return testSetUp.basicSetUp(dropDB, master, replica); + } + + @After + public void tearDown() throws Exception { + testSetUp.tearDown(); + } + + protected ReplicableImpl createNewMaster() throws Exception { + return testSetUp.createNewMaster(); + } + + protected ReplicableImpl createNewReplica() throws Exception { + return testSetUp.createNewReplica(); + } + + protected void persistenceSetUp(boolean dropDB) { + testSetUp.persistenceSetUp(dropDB); + } + + protected void persistenceTearDown() { + testSetUp.persistenceTearDown(); + } + + public void stopReplicatingToMaster() throws IOException { + testSetUp.stopReplicatingToMaster(); + } +} diff --git a/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF b/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF index 8945be6f73c..a73b2c7998e 100644 --- a/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF @@ -12,4 +12,4 @@ Require-Bundle: org.junit4;bundle-version="4.8.2", com.rabbitmq.client, org.mongodb.mongo-java-driver;bundle-version="2.13.0", com.sap.sse.replication.testsupport, - com.sap.sse.replication.testsupport + com.sap.sse.mail.replication.testsupport diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java index 14eccc103aa..af5d80c108d 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java @@ -4,31 +4,37 @@ import java.io.IOException; import java.net.MalformedURLException; import com.sap.sse.mongodb.MongoDBService; -import com.sap.sse.replication.testsupport.AbstractServerReplicationTest; +import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; import com.sap.sse.security.SecurityService; import com.sap.sse.security.impl.SecurityServiceImpl; import com.sap.sse.security.userstore.mongodb.UserStoreImpl; -public abstract class AbstractSecurityReplicationTest extends AbstractServerReplicationTest { - private MongoDBService mongoDBService; +public abstract class AbstractSecurityReplicationTest extends AbstractServerWithSingleServiceReplicationTest { + public AbstractSecurityReplicationTest() { + super(new ServerReplicationTestSetUp()); + } - @Override - protected void persistenceSetUp(boolean dropDB) { - mongoDBService = MongoDBService.INSTANCE; - if (dropDB) { - mongoDBService.getDB().dropDatabase(); + private static class ServerReplicationTestSetUp extends com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp { + private MongoDBService mongoDBService; + + @Override + protected void persistenceSetUp(boolean dropDB) { + mongoDBService = MongoDBService.INSTANCE; + if (dropDB) { + mongoDBService.getDB().dropDatabase(); + } + } + + @Override + protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { + SecurityServiceImpl result = new SecurityServiceImpl(new UserStoreImpl()); + result.clearReplicaState(); + return result; + } + + @Override + protected SecurityServiceImpl createNewReplica() { + return new SecurityServiceImpl(new UserStoreImpl()); } } - - @Override - protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { - SecurityServiceImpl result = new SecurityServiceImpl(new UserStoreImpl()); - result.clearReplicaState(); - return result; - } - - @Override - protected SecurityServiceImpl createNewReplica() { - return new SecurityServiceImpl(new UserStoreImpl()); - } } From de39d6a40a524e8f524d4068f30f54892ef8bec0 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Fri, 13 Feb 2015 17:59:57 +0100 Subject: [PATCH 110/264] refactor AbstractServerReplicationTest to enable tests with multiple replicable services in future --- .../test/AbstractLogReplicationTest.java | 3 +- .../test/AbstractServerReplicationTest.java | 59 ++++++++------- .../test/ConnectionResetAndReconnectTest.java | 26 ++++--- ...LeaderboardCorrectionsReplicationTest.java | 3 +- ...tialLoadReplicationObjectIdentityTest.java | 11 +-- .../test/PrematureOperationReceiptTest.java | 3 +- .../test/RaceLogReplicationTest.java | 5 +- .../test/RegattaLogReplicationTest.java | 3 +- ...orageManagementServiceReplicationTest.java | 72 ++++++++++--------- ...> AbstractServerReplicationTestSetUp.java} | 53 ++++++++++---- ...erWithMultipleServicesReplicationTest.java | 25 +++++++ ...erverWithSingleServiceReplicationTest.java | 63 ++++++++++++++++ .../META-INF/MANIFEST.MF | 2 +- .../test/AbstractSecurityReplicationTest.java | 46 ++++++------ 14 files changed, 260 insertions(+), 114 deletions(-) rename java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/{AbstractServerReplicationTest.java => AbstractServerReplicationTestSetUp.java} (92%) mode change 100755 => 100644 create mode 100644 java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java create mode 100755 java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java index 44d0a431ec4..5d9524cd142 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java @@ -35,8 +35,9 @@ public abstract class AbstractLogReplicationTest { - protected MongoDBService mongoDBService; - protected MongoObjectFactory mongoObjectFactory; +public abstract class AbstractServerReplicationTest extends com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest { + protected ServerReplicationTestSetUp testSetUp; - /** - * Drops the test DB, if dropDB is true and requests the DB to start. - */ - @Override - protected void persistenceSetUp(boolean dropDB) { - mongoDBService = MongoDBService.INSTANCE; - if (dropDB) { - mongoDBService.getDB().dropDatabase(); - } - mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService); + public AbstractServerReplicationTest() { + super(new ServerReplicationTestSetUp()); + testSetUp = (ServerReplicationTestSetUp) super.testSetUp; } - protected RacingEventServiceImpl createNewMaster() { - return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, - DomainFactory.INSTANCE), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), - EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); - } - - protected RacingEventServiceImpl createNewReplica() { - return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, - // replica gets its own base DomainFactory: - new DomainFactoryImpl()), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), - EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + protected static class ServerReplicationTestSetUp extends com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp { + protected MongoDBService mongoDBService; + protected MongoObjectFactory mongoObjectFactory; + + /** + * Drops the test DB, if dropDB is true and requests the DB to start. + */ + @Override + protected void persistenceSetUp(boolean dropDB) { + mongoDBService = MongoDBService.INSTANCE; + if (dropDB) { + mongoDBService.getDB().dropDatabase(); + } + mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoDBService); + } + + @Override + public RacingEventServiceImpl createNewMaster() { + return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, + DomainFactory.INSTANCE), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), + EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + } + + @Override + public RacingEventServiceImpl createNewReplica() { + return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, + // replica gets its own base DomainFactory: + new DomainFactoryImpl()), mongoObjectFactory, MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), + EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + } } } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java index 30ce408fa99..65da3647ea9 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java @@ -81,16 +81,20 @@ public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTe } - @Before - @Override - public void setUp() throws Exception { - try { - Pair, ReplicationMasterDescriptor> result = basicSetUp(/* dropDB */ true, - /* master=null means create a new one */ null, /* replica=null means create a new one */null); - masterDescriptor = MasterReplicationDescriptorMock.from(result.getB()); - } catch (Exception e) { - e.printStackTrace(); - tearDown(); + protected static class ServerReplicationTestSetUp extends + com.sap.sailing.server.replication.test.AbstractServerReplicationTest.ServerReplicationTestSetUp { + @Before + @Override + public void setUp() throws Exception { + try { + Pair, ReplicationMasterDescriptor> result = basicSetUp( + /* dropDB */true, + /* master=null means create a new one */null, /* replica=null means create a new one */null); + masterDescriptor = MasterReplicationDescriptorMock.from(result.getB()); + } catch (Exception e) { + e.printStackTrace(); + tearDown(); + } } } @@ -102,7 +106,7 @@ public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTe /* until here both instances should have the same in-memory state. * now lets add an event on master and stop the messaging queue. */ stopMessagingExchange(); - replicaReplicator.startToReplicateFrom(masterDescriptor); + testSetUp.getReplicaReplicator().startToReplicateFrom(testSetUp.getMasterDescriptor()); Event event = addEventOnMaster(); Thread.sleep(1000); // wait for master queue to get filled assertNull(replica.getEvent(event.getId())); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java index c28f2ea50e6..9bb2e1db068 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/DelayedLeaderboardCorrectionsReplicationTest.java @@ -46,6 +46,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.mongodb.MongoDBService; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.ReplicationService; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class DelayedLeaderboardCorrectionsReplicationTest extends AbstractServerReplicationTest { private static final Logger logger = Logger.getLogger(DelayedLeaderboardCorrectionsReplicationTest.class.getName()); @@ -115,7 +116,7 @@ public class DelayedLeaderboardCorrectionsReplicationTest extends AbstractServer masterLeaderboardReloaded.getRaceColumnByName(Q2), MillisecondsTimePoint.now())); // replicate the re-loaded environment - Pair, ReplicationMasterDescriptor> descriptors = basicSetUp( + Pair, ReplicationMasterDescriptor> descriptors = basicSetUp( /* dropDB */false, master, replica); replicaReplicator = descriptors.getA(); masterDescriptor = descriptors.getB(); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java index 862220bc514..a2b082373bd 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java @@ -73,9 +73,10 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.impl.ReplicationReceiver; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class InitialLoadReplicationObjectIdentityTest extends AbstractServerReplicationTest { - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; + private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; /** * Drops the test DB. Sets up master and replica, starts the JMS message broker and registers the replica with the master. @@ -84,10 +85,10 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl @Override public void setUp() throws Exception { persistenceSetUp(/* dropDB */ true); - this.master = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE - .getMongoObjectFactory(mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); - this.replica = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE - .getMongoObjectFactory(mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + this.master = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(testSetUp.mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE + .getMongoObjectFactory(testSetUp.mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(testSetUp.mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); + this.replica = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(testSetUp.mongoDBService, DomainFactory.INSTANCE), PersistenceFactory.INSTANCE + .getMongoObjectFactory(testSetUp.mongoDBService), MediaDBFactory.INSTANCE.getMediaDB(testSetUp.mongoDBService), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); } private void performReplicationSetup() throws Exception { diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java index 6e640221b30..325e88864d0 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java @@ -15,6 +15,7 @@ import com.sap.sailing.server.operationaltransformation.CreateFlexibleLeaderboar import com.sap.sse.common.Util.Pair; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.impl.ReplicationReceiver; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class PrematureOperationReceiptTest extends AbstractServerReplicationTest { private ReplicationReceiver replicator; @@ -25,7 +26,7 @@ public class PrematureOperationReceiptTest extends AbstractServerReplicationTest @Before @Override public void setUp() throws Exception { - Pair, ReplicationMasterDescriptor> result = + Pair, ReplicationMasterDescriptor> result = basicSetUp(/* dropDB */ true, /* master=null means create a new one */ null, /* replica=null means create a new one */ null); replicator = replicaReplicator.startToReplicateFromButDontYetFetchInitialLoad(result.getB(), /* startReplicatorSuspended */ true); } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java index 9660d8b92f3..791aaa82e29 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java @@ -47,10 +47,11 @@ import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.replication.ReplicationMasterDescriptor; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class RaceLogReplicationTest extends AbstractLogReplicationTest { - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; + private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; private RaceLogEvent raceLogEvent; private RaceLogEvent anotherRaceLogEvent; @@ -217,7 +218,7 @@ public class RaceLogReplicationTest extends AbstractLogReplicationTest { - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; + private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; private RegattaLogEvent regattaLogEvent; // private RegattaLogEvent anotherRegattaLogEvent; diff --git a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java index be1aba6fc9a..b8e02563ed2 100755 --- a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java +++ b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/replication/test/AbstractFileStorageManagementServiceReplicationTest.java @@ -10,43 +10,49 @@ import com.sap.sse.filestorage.impl.EmptyFileStorageServicePropertyStoreImpl; import com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl; import com.sap.sse.filestorage.testsupport.DummyFileStorageService; import com.sap.sse.mongodb.MongoDBService; -import com.sap.sse.replication.testsupport.AbstractServerReplicationTest; +import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; public abstract class AbstractFileStorageManagementServiceReplicationTest extends - AbstractServerReplicationTest { - private MongoDBService mongoDBService; + AbstractServerWithSingleServiceReplicationTest { + public AbstractFileStorageManagementServiceReplicationTest() { + super(new ServerReplicationTestSetUp()); + } - @Override - protected void persistenceSetUp(boolean dropDB) { - mongoDBService = MongoDBService.INSTANCE; - if (dropDB) { + private static class ServerReplicationTestSetUp extends com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp { + private MongoDBService mongoDBService; + + @Override + protected void persistenceSetUp(boolean dropDB) { + mongoDBService = MongoDBService.INSTANCE; + if (dropDB) { + mongoDBService.getDB().dropDatabase(); + } + } + + protected FileStorageManagementServiceImpl createNewService() { + return new FileStorageManagementServiceImpl( + new SingleTypeBasedServiceFinderImpl<>(new DummyFileStorageService(), + DummyFileStorageService.NAME), EmptyFileStorageServicePropertyStoreImpl.INSTANCE); + } + + @Override + public FileStorageManagementServiceImpl createNewMaster() throws MalformedURLException, IOException, + InterruptedException { + FileStorageManagementServiceImpl result = createNewService(); + FileStorageService serviceOnMaster = result.getAvailableFileStorageServices()[0]; + result.setActiveFileStorageService(serviceOnMaster); + result.setFileStorageServiceProperty(serviceOnMaster, DummyFileStorageService.PROPERTY_NAME, "123"); + return result; + } + + @Override + public FileStorageManagementServiceImpl createNewReplica() { + return createNewService(); + } + + @Override + protected void persistenceTearDown() { mongoDBService.getDB().dropDatabase(); } } - - protected FileStorageManagementServiceImpl createNewService() { - return new FileStorageManagementServiceImpl( - new SingleTypeBasedServiceFinderImpl<>(new DummyFileStorageService(), - DummyFileStorageService.NAME), EmptyFileStorageServicePropertyStoreImpl.INSTANCE); - } - - @Override - protected FileStorageManagementServiceImpl createNewMaster() throws MalformedURLException, IOException, - InterruptedException { - FileStorageManagementServiceImpl result = createNewService(); - FileStorageService serviceOnMaster = result.getAvailableFileStorageServices()[0]; - result.setActiveFileStorageService(serviceOnMaster); - result.setFileStorageServiceProperty(serviceOnMaster, DummyFileStorageService.PROPERTY_NAME, "123"); - return result; - } - - @Override - protected FileStorageManagementServiceImpl createNewReplica() { - return createNewService(); - } - - @Override - protected void persistenceTearDown() { - mongoDBService.getDB().dropDatabase(); - } } diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTest.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java old mode 100755 new mode 100644 similarity index 92% rename from java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTest.java rename to java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java index 001315e97a8..2145e775726 --- a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTest.java +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java @@ -22,8 +22,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPOutputStream; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.rules.Timeout; @@ -42,10 +40,12 @@ import com.sap.sse.replication.impl.ReplicationReceiver; import com.sap.sse.replication.impl.ReplicationServiceImpl; import com.sap.sse.replication.impl.SingletonReplicablesProvider; -public abstract class AbstractServerReplicationTest, ReplicableImpl extends ReplicableInterface> { - private static final Logger logger = Logger.getLogger(AbstractServerReplicationTest.class.getName()); +public abstract class AbstractServerReplicationTestSetUp, ReplicableImpl extends ReplicableInterface> { + private static final Logger logger = Logger.getLogger(AbstractServerReplicationTestSetUp.class.getName()); - protected static final int SERVLET_PORT = 9990; + protected static final int DEFAULT_SERVLET_PORT = 9990; + + private final int servletPort; protected ReplicableImpl replica; protected ReplicableImpl master; protected ReplicationServiceTestImpl replicaReplicator; @@ -53,6 +53,14 @@ public abstract class AbstractServerReplicationTest, ReplicationMasterDescriptor> result = basicSetUp( @@ -120,9 +127,9 @@ public abstract class AbstractServerReplicationTest replicaReplicator = new ReplicationServiceTestImpl(exchangeName, exchangeHost, rim, replicaDescriptor, - this.replica, this.master, masterReplicator, masterDescriptor); + this.replica, this.master, masterReplicator, masterDescriptor, servletPort); Pair, ReplicationMasterDescriptor> result = new Pair<>(replicaReplicator, masterDescriptor); logger.info("starting initial load transmission servlet for "+getClass().getName()); initialLoadTestServerThread = replicaReplicator.startInitialLoadTransmissionServlet(); @@ -136,16 +143,15 @@ public abstract class AbstractServerReplicationTest getReplicaReplicator() { + return replicaReplicator; + } + + public ReplicationMasterDescriptor getMasterDescriptor() { + return masterDescriptor; + } } diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java new file mode 100644 index 00000000000..125c97f6dee --- /dev/null +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithMultipleServicesReplicationTest.java @@ -0,0 +1,25 @@ +package com.sap.sse.replication.testsupport; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; + +public abstract class AbstractServerWithMultipleServicesReplicationTest { + protected final Set> testSetUps = new HashSet<>(); + + @Before + public void setUp() throws Exception { + for (AbstractServerReplicationTestSetUp testSetUp : testSetUps) { + testSetUp.setUp(); + } + } + + @After + public void tearDown() throws Exception { + for (AbstractServerReplicationTestSetUp testSetUp : testSetUps) { + testSetUp.tearDown(); + } + } +} diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java new file mode 100755 index 00000000000..2e20ed3ee1c --- /dev/null +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerWithSingleServiceReplicationTest.java @@ -0,0 +1,63 @@ +package com.sap.sse.replication.testsupport; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; + +import com.sap.sse.common.Util.Pair; +import com.sap.sse.replication.Replicable; +import com.sap.sse.replication.ReplicationMasterDescriptor; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; + +public abstract class AbstractServerWithSingleServiceReplicationTest, ReplicableImpl extends ReplicableInterface> { + protected final AbstractServerReplicationTestSetUp testSetUp; + protected ReplicableImpl replica; + protected ReplicableImpl master; + protected ReplicationServiceTestImpl replicaReplicator; + protected ReplicationMasterDescriptor masterDescriptor; + + + public AbstractServerWithSingleServiceReplicationTest(AbstractServerReplicationTestSetUp testSetUp) { + this.testSetUp = testSetUp; + } + + @Before + public void setUp() throws Exception { + testSetUp.setUp(); + master = testSetUp.getMaster(); + replica = testSetUp.getReplica(); + replicaReplicator = testSetUp.getReplicaReplicator(); + masterDescriptor = testSetUp.getMasterDescriptor(); + } + + protected Pair, ReplicationMasterDescriptor> basicSetUp(boolean dropDB, + ReplicableImpl master, ReplicableImpl replica) throws Exception { + return testSetUp.basicSetUp(dropDB, master, replica); + } + + @After + public void tearDown() throws Exception { + testSetUp.tearDown(); + } + + protected ReplicableImpl createNewMaster() throws Exception { + return testSetUp.createNewMaster(); + } + + protected ReplicableImpl createNewReplica() throws Exception { + return testSetUp.createNewReplica(); + } + + protected void persistenceSetUp(boolean dropDB) { + testSetUp.persistenceSetUp(dropDB); + } + + protected void persistenceTearDown() { + testSetUp.persistenceTearDown(); + } + + public void stopReplicatingToMaster() throws IOException { + testSetUp.stopReplicatingToMaster(); + } +} diff --git a/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF b/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF index 8945be6f73c..a73b2c7998e 100644 --- a/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF @@ -12,4 +12,4 @@ Require-Bundle: org.junit4;bundle-version="4.8.2", com.rabbitmq.client, org.mongodb.mongo-java-driver;bundle-version="2.13.0", com.sap.sse.replication.testsupport, - com.sap.sse.replication.testsupport + com.sap.sse.mail.replication.testsupport diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java index 14eccc103aa..af5d80c108d 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java @@ -4,31 +4,37 @@ import java.io.IOException; import java.net.MalformedURLException; import com.sap.sse.mongodb.MongoDBService; -import com.sap.sse.replication.testsupport.AbstractServerReplicationTest; +import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; import com.sap.sse.security.SecurityService; import com.sap.sse.security.impl.SecurityServiceImpl; import com.sap.sse.security.userstore.mongodb.UserStoreImpl; -public abstract class AbstractSecurityReplicationTest extends AbstractServerReplicationTest { - private MongoDBService mongoDBService; +public abstract class AbstractSecurityReplicationTest extends AbstractServerWithSingleServiceReplicationTest { + public AbstractSecurityReplicationTest() { + super(new ServerReplicationTestSetUp()); + } - @Override - protected void persistenceSetUp(boolean dropDB) { - mongoDBService = MongoDBService.INSTANCE; - if (dropDB) { - mongoDBService.getDB().dropDatabase(); + private static class ServerReplicationTestSetUp extends com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp { + private MongoDBService mongoDBService; + + @Override + protected void persistenceSetUp(boolean dropDB) { + mongoDBService = MongoDBService.INSTANCE; + if (dropDB) { + mongoDBService.getDB().dropDatabase(); + } + } + + @Override + protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { + SecurityServiceImpl result = new SecurityServiceImpl(new UserStoreImpl()); + result.clearReplicaState(); + return result; + } + + @Override + protected SecurityServiceImpl createNewReplica() { + return new SecurityServiceImpl(new UserStoreImpl()); } } - - @Override - protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { - SecurityServiceImpl result = new SecurityServiceImpl(new UserStoreImpl()); - result.clearReplicaState(); - return result; - } - - @Override - protected SecurityServiceImpl createNewReplica() { - return new SecurityServiceImpl(new UserStoreImpl()); - } } From 42e1411bb6693cd0f683a38b5336d75b3125c502 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Fri, 13 Feb 2015 20:30:32 +0100 Subject: [PATCH 111/264] add replication test for mail service (could only test applying changes to master) --- .../.classpath | 7 + .../.project | 28 ++++ .../.settings/org.eclipse.jdt.core.prefs | 7 + .../.settings/org.eclipse.pde.core.prefs | 3 + .../META-INF/MANIFEST.MF | 11 ++ .../build.properties | 4 + .../com.sap.sse.mail.replication.test/pom.xml | 13 ++ .../.classpath | 7 + .../.project | 28 ++++ .../.settings/org.eclipse.jdt.core.prefs | 7 + .../.settings/org.eclipse.pde.core.prefs | 3 + .../META-INF/MANIFEST.MF | 17 ++ .../build.properties | 4 + .../pom.xml | 13 ++ .../AbstractMailReplicationTest.java | 59 +++++++ java/com.sap.sse.mail/META-INF/MANIFEST.MF | 3 +- .../sap/sse/mail/impl/MailServiceImpl.java | 22 +-- .../META-INF/MANIFEST.MF | 8 +- .../test/AbstractSecurityReplicationTest.java | 5 +- ...licationLeadingToEmailReplicationTest.java | 148 ++++++++++++++++++ .../security/impl/SecurityServiceImpl.java | 11 +- java/pom.xml | 4 +- 22 files changed, 394 insertions(+), 18 deletions(-) create mode 100755 java/com.sap.sse.mail.replication.test/.classpath create mode 100755 java/com.sap.sse.mail.replication.test/.project create mode 100755 java/com.sap.sse.mail.replication.test/.settings/org.eclipse.jdt.core.prefs create mode 100755 java/com.sap.sse.mail.replication.test/.settings/org.eclipse.pde.core.prefs create mode 100644 java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF create mode 100755 java/com.sap.sse.mail.replication.test/build.properties create mode 100755 java/com.sap.sse.mail.replication.test/pom.xml create mode 100755 java/com.sap.sse.mail.replication.testsupport/.classpath create mode 100755 java/com.sap.sse.mail.replication.testsupport/.project create mode 100755 java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.jdt.core.prefs create mode 100755 java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.pde.core.prefs create mode 100644 java/com.sap.sse.mail.replication.testsupport/META-INF/MANIFEST.MF create mode 100755 java/com.sap.sse.mail.replication.testsupport/build.properties create mode 100755 java/com.sap.sse.mail.replication.testsupport/pom.xml create mode 100644 java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailReplicationTest.java create mode 100644 java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java diff --git a/java/com.sap.sse.mail.replication.test/.classpath b/java/com.sap.sse.mail.replication.test/.classpath new file mode 100755 index 00000000000..eca7bdba8f0 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sse.mail.replication.test/.project b/java/com.sap.sse.mail.replication.test/.project new file mode 100755 index 00000000000..51fa2fe50fb --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/.project @@ -0,0 +1,28 @@ + + + com.sap.sse.mail.replication.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF b/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..29aa5418f08 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Test +Bundle-SymbolicName: com.sap.sse.mail.replication.test +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Fragment-Host: com.sap.sse.mail +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: org.junit4;bundle-version="4.8.2", + com.rabbitmq.client, + com.sap.sse.replication.testsupport diff --git a/java/com.sap.sse.mail.replication.test/build.properties b/java/com.sap.sse.mail.replication.test/build.properties new file mode 100755 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sse.mail.replication.test/pom.xml b/java/com.sap.sse.mail.replication.test/pom.xml new file mode 100755 index 00000000000..eb287b53335 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sse.mail.replication.test + eclipse-plugin + diff --git a/java/com.sap.sse.mail.replication.testsupport/.classpath b/java/com.sap.sse.mail.replication.testsupport/.classpath new file mode 100755 index 00000000000..eca7bdba8f0 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sse.mail.replication.testsupport/.project b/java/com.sap.sse.mail.replication.testsupport/.project new file mode 100755 index 00000000000..1d1e9965612 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/.project @@ -0,0 +1,28 @@ + + + com.sap.sse.mail.replication.testsupport + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sse.mail.replication.testsupport/META-INF/MANIFEST.MF b/java/com.sap.sse.mail.replication.testsupport/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..84cf686c18a --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/META-INF/MANIFEST.MF @@ -0,0 +1,17 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Test +Bundle-SymbolicName: com.sap.sse.mail.replication.testsupport +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: com.sap.sse.replication, + com.sap.sse.replication.testsupport, + com.sap.sse.mail, + com.sap.sse, + com.sap.sse.common, + org.hamcrest;bundle-version="1.1.0", + org.junit4;bundle-version="4.8.2", + org.mockito.mockito-core;bundle-version="1.10.14", + com.sun.mail.javax.mail;bundle-version="1.4.5" +Export-Package: com.sap.sse.mail.replication.testsupport diff --git a/java/com.sap.sse.mail.replication.testsupport/build.properties b/java/com.sap.sse.mail.replication.testsupport/build.properties new file mode 100755 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sse.mail.replication.testsupport/pom.xml b/java/com.sap.sse.mail.replication.testsupport/pom.xml new file mode 100755 index 00000000000..2ed85a10b24 --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sse.mail.replication.testsupport + eclipse-plugin + diff --git a/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailReplicationTest.java b/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailReplicationTest.java new file mode 100644 index 00000000000..3777cff986a --- /dev/null +++ b/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailReplicationTest.java @@ -0,0 +1,59 @@ +package com.sap.sse.mail.replication.testsupport; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.MailService; +import com.sap.sse.mail.impl.MailServiceImpl; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp; +import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; + +public class AbstractMailReplicationTest extends AbstractServerWithSingleServiceReplicationTest { + public AbstractMailReplicationTest() { + super(new MailServerReplicationTestSetUp()); + } + + public static final Map numberOfMailsSent = new HashMap<>(); + + @Before + public void setUp() { + numberOfMailsSent.clear(); + } + + public static MailServiceImpl createMailCountingService(final boolean canSendMail) { + return new MailServiceImpl(null) { + @Override + protected void internalSendMail(String toAddress, String subject, ContentSetter contentSetter) + throws MailException { + if (canSendMail) { + Integer old = numberOfMailsSent.get(this); + numberOfMailsSent.put(this, old == null ? 1 : old + 1); + } + } + }; + } + + public static class MailServerReplicationTestSetUp extends AbstractServerReplicationTestSetUp { + public MailServerReplicationTestSetUp() { + super(); + } + + public MailServerReplicationTestSetUp(int servletPort) { + super(servletPort); + } + + @Override + protected MailServiceImpl createNewMaster() throws MailException { + return createMailCountingService(true); + } + + @Override + protected MailServiceImpl createNewReplica() throws MailException { + return createMailCountingService(false); + } + } + +} diff --git a/java/com.sap.sse.mail/META-INF/MANIFEST.MF b/java/com.sap.sse.mail/META-INF/MANIFEST.MF index ccf3a8d0850..d5aff706f24 100755 --- a/java/com.sap.sse.mail/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.mail/META-INF/MANIFEST.MF @@ -6,7 +6,8 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-Activator: com.sap.sse.mail.impl.Activator -Export-Package: com.sap.sse.mail +Export-Package: com.sap.sse.mail, + com.sap.sse.mail.impl Import-Package: javax.mail, javax.mail.internet, org.osgi.framework diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java index 7ef047c3a5c..4ac702b4ef6 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java @@ -6,6 +6,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.MalformedURLException; +import java.util.HashSet; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -26,9 +27,8 @@ 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.util.ClearStateTestSupport; -public class MailServiceImpl implements ReplicableMailService, ClearStateTestSupport { +public class MailServiceImpl implements ReplicableMailService { private static final Logger logger = Logger.getLogger(MailServiceImpl.class.getName()); private Properties mailProperties; @@ -39,7 +39,7 @@ public class MailServiceImpl implements ReplicableMailService, ClearStateTestSup */ private ReplicationMasterDescriptor replicatingFromMaster; private final ConcurrentHashMap, OperationExecutionListener> operationExecutionListeners; - private Set> operationsSentToMasterForReplication; + private final Set> operationsSentToMasterForReplication = new HashSet<>(); private ThreadLocal currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = ThreadLocal .withInitial(() -> false); @@ -56,12 +56,17 @@ public class MailServiceImpl implements ReplicableMailService, ClearStateTestSup } } - private static interface ContentSetter { + protected static interface ContentSetter { void setContent(MimeMessage msg) throws MessagingException; } - private void internalSendMail(String toAddress, String subject, ContentSetter contentSetter) throws MailException { - if (mailProperties != null && mailProperties.containsKey("mail.transport.protocol")) { + private boolean canSendMail() { + return mailProperties != null && mailProperties.containsKey("mail.transport.protocol"); + } + + //protected for testing purposes + protected void internalSendMail(String toAddress, String subject, ContentSetter contentSetter) throws MailException { + if (canSendMail()) { if (toAddress != null) { Session session = Session.getInstance(mailProperties, new SMTPAuthenticator()); MimeMessage msg = new MimeMessage(session); @@ -201,9 +206,4 @@ public class MailServiceImpl implements ReplicableMailService, ClearStateTestSup this.currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster .set(currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster); } - - @Override - public void clearState() throws Exception { - mailProperties.clear(); - } } diff --git a/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF b/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF index a73b2c7998e..69111890757 100644 --- a/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security.replication.test/META-INF/MANIFEST.MF @@ -6,10 +6,14 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Fragment-Host: com.sap.sse.security Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Require-Bundle: org.junit4;bundle-version="4.8.2", +Require-Bundle: org.hamcrest;bundle-version="1.1.0", + org.junit4;bundle-version="4.8.2", com.sap.sse.security.userstore.mongodb, com.sap.sse.mongodb, com.rabbitmq.client, org.mongodb.mongo-java-driver;bundle-version="2.13.0", com.sap.sse.replication.testsupport, - com.sap.sse.mail.replication.testsupport + com.sap.sse.mail.replication.testsupport, + org.objenesis;bundle-version="2.1.0", + org.mockito.mockito-core;bundle-version="1.10.14" +Import-Package: org.osgi.util.tracker;version="1.5.1" diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java index af5d80c108d..97fec10c49c 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/AbstractSecurityReplicationTest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.net.MalformedURLException; import com.sap.sse.mongodb.MongoDBService; +import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp; import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; import com.sap.sse.security.SecurityService; import com.sap.sse.security.impl.SecurityServiceImpl; @@ -11,10 +12,10 @@ import com.sap.sse.security.userstore.mongodb.UserStoreImpl; public abstract class AbstractSecurityReplicationTest extends AbstractServerWithSingleServiceReplicationTest { public AbstractSecurityReplicationTest() { - super(new ServerReplicationTestSetUp()); + super(new SecurityServerReplicationTestSetUp()); } - private static class ServerReplicationTestSetUp extends com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp { + public static class SecurityServerReplicationTestSetUp extends AbstractServerReplicationTestSetUp { private MongoDBService mongoDBService; @Override diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java new file mode 100644 index 00000000000..87faef2348c --- /dev/null +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java @@ -0,0 +1,148 @@ +package com.sap.sse.security.replication.test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.net.MalformedURLException; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.osgi.util.tracker.ServiceTracker; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.MailService; +import com.sap.sse.mail.impl.MailServiceImpl; +import com.sap.sse.mail.replication.testsupport.AbstractMailReplicationTest; +import com.sap.sse.replication.testsupport.AbstractServerWithMultipleServicesReplicationTest; +import com.sap.sse.security.SecurityService; +import com.sap.sse.security.impl.SecurityServiceImpl; +import com.sap.sse.security.shared.UserManagementException; +import com.sap.sse.security.userstore.mongodb.UserStoreImpl; + +/** + * See if transitively triggered email operation ultimately behaves well and results in only one outbound email. + * + * @author Fredrik Teschke + */ +public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractServerWithMultipleServicesReplicationTest { + private static final SecurityServerReplicationTestSetUp securitySetUp = new SecurityServerReplicationTestSetUp(); + private static final MailServerReplicationTestSetUp mailSetUp = new MailServerReplicationTestSetUp(); + + private static MailServiceImpl masterMailService; + private static MailServiceImpl replicaMailService; + + @Before + @Override + public void setUp() throws Exception { + masterMailService = AbstractMailReplicationTest.createMailCountingService(true); + replicaMailService = AbstractMailReplicationTest.createMailCountingService(false); + super.setUp(); + } + + private static class SecurityServerReplicationTestSetUp extends + AbstractSecurityReplicationTest.SecurityServerReplicationTestSetUp { + @Override + protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { + @SuppressWarnings("unchecked") + ServiceTracker trackerMock = mock(ServiceTracker.class); + doReturn(masterMailService).when(trackerMock).getService(); + SecurityServiceImpl result = new SecurityServiceImpl(trackerMock, new UserStoreImpl()); + result.clearReplicaState(); + return result; + } + + @Override + protected SecurityServiceImpl createNewReplica() { + @SuppressWarnings("unchecked") + ServiceTracker trackerMock = mock(ServiceTracker.class); + doReturn(replicaMailService).when(trackerMock).getService(); + SecurityServiceImpl result = new SecurityServiceImpl(trackerMock, new UserStoreImpl()); + return result; + } + } + + private static class MailServerReplicationTestSetUp extends AbstractMailReplicationTest.MailServerReplicationTestSetUp { + private static final int SERVLET_PORT = 9991; + + public MailServerReplicationTestSetUp() { + super(SERVLET_PORT); + } + + @Override + protected MailServiceImpl createNewMaster() throws MailException { + return masterMailService; + } + + @Override + protected MailServiceImpl createNewReplica() throws MailException { + return replicaMailService; + } + } + + public SecurityReplicationLeadingToEmailReplicationTest() { + testSetUps.add(securitySetUp); + testSetUps.add(mailSetUp); + } + + /** + * See if sending an email (triggered by creating the user on the master) results in only one mail being sent + * (on the master). + */ + @Test + public void triggerEmailSendByAddingUserOnMaster() + throws UserManagementException, MailException, IllegalAccessException, InterruptedException { + SecurityService masterSecurityService = securitySetUp.getMaster(); + + final String username = "Ernie"; + final String email = "ernie@sesame-street.com"; + final String password = "BertMyFriend"; + final String validationBaseURL = null; //so that validation email is not sent + + masterSecurityService.createSimpleUser(username, email, password, validationBaseURL); + + masterSecurityService.sendMail(username, "subject", "body"); + + securitySetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + mailSetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + Thread.sleep(3000); + + assertThat("mail was not sent on replica", + AbstractMailReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null)); + assertThat("mail was sent on master", + AbstractMailReplicationTest.numberOfMailsSent.get(masterMailService), equalTo(1)); + } + + + /** + * Try the other way round - have to ignore for now, no replication test seems to try applying + * operations on the replica. + */ + @Test + @Ignore + public void triggerEmailSendByAddingUserOnReplica() + throws UserManagementException, MailException, IllegalAccessException, InterruptedException { + SecurityService replicaSecurityService = securitySetUp.getReplica(); + + final String username = "Ernie"; + final String email = "ernie@sesame-street.com"; + final String password = "BertMyFriend"; + final String validationBaseURL = null; //so that validation email is not sent + + replicaSecurityService.createSimpleUser(username, email, password, validationBaseURL); + + replicaSecurityService.sendMail(username, "subject", "body"); + + securitySetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + mailSetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + Thread.sleep(3000); + + assertThat("mail was not sent on replica", + AbstractMailReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null)); + assertThat("mail was sent on master", + AbstractMailReplicationTest.numberOfMailsSent.get(masterMailService), equalTo(1)); + } +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index 00ba1efcd8a..9424aed58a4 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -185,6 +185,10 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica } } } + + private MailService getMailService() { + return mailServiceTracker == null ? null : mailServiceTracker.getService(); + } @Override public void sendMail(String username, String subject, String body) throws MailException { @@ -192,7 +196,12 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica if (user != null) { final String toAddress = user.getEmail(); if (toAddress != null) { - mailServiceTracker.getService().sendMail(username, subject, body); + MailService mailService = getMailService(); + if (mailService == null) { + logger.warning(String.format("Could not send mail to user %s: no MailService found", username)); + } else { + getMailService().sendMail(username, subject, body); + } } } } diff --git a/java/pom.xml b/java/pom.xml index d3fbc338c41..6cc686d9d16 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -16,7 +16,9 @@ com.sap.sse com.sap.sse.common com.sap.sse.shared.android - com.sap.sse.mail + com.sap.sse.mail + com.sap.sse.mail.replication.test + com.sap.sse.mail.replicadtion.testsupport com.sap.sse.mongodb com.sap.sse.gwt com.sap.sse.gwt.adminconsole From 1d99e6ba75f69e253e315a68d26c14e3b7ab3aca Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Fri, 13 Feb 2015 20:59:11 +0100 Subject: [PATCH 112/264] also add mail replication test that tests outside of SecurityService context --- .../META-INF/MANIFEST.MF | 6 ++-- .../SimpleMailServiceReplicationTest.java | 28 +++++++++++++++++ ...> AbstractMailServiceReplicationTest.java} | 6 ++-- ...licationLeadingToEmailReplicationTest.java | 30 +++++++++++-------- 4 files changed, 52 insertions(+), 18 deletions(-) create mode 100755 java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java rename java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/{AbstractMailReplicationTest.java => AbstractMailServiceReplicationTest.java} (88%) diff --git a/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF b/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF index 29aa5418f08..2757ce82521 100644 --- a/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.mail.replication.test/META-INF/MANIFEST.MF @@ -6,6 +6,8 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Fragment-Host: com.sap.sse.mail Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Require-Bundle: org.junit4;bundle-version="4.8.2", +Require-Bundle: org.hamcrest;bundle-version="1.1.0", + org.junit4;bundle-version="4.8.2", com.rabbitmq.client, - com.sap.sse.replication.testsupport + com.sap.sse.replication.testsupport, + com.sap.sse.mail.replication.testsupport diff --git a/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java b/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java new file mode 100755 index 00000000000..2cc72bad1b1 --- /dev/null +++ b/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java @@ -0,0 +1,28 @@ +package com.sap.sse.mail.replication.test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +import org.junit.Test; + +import com.sap.sse.common.mail.MailException; +import com.sap.sse.mail.MailService; +import com.sap.sse.mail.replication.testsupport.AbstractMailServiceReplicationTest; + +public class SimpleMailServiceReplicationTest extends AbstractMailServiceReplicationTest { + @Test + public void testReplicationOfSendMail() throws InterruptedException, + IllegalAccessException, MailException { + MailService master = testSetUp.getMaster(); + MailService replica = testSetUp.getReplica(); + master.sendMail("test@ing.me", "oh what a beatiful mail", "to be sent on this day"); + + testSetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + Thread.sleep(3000); + + assertThat("mail was not sent on replica", + AbstractMailServiceReplicationTest.numberOfMailsSent.get(replica), equalTo(null)); + assertThat("mail was sent on master", + AbstractMailServiceReplicationTest.numberOfMailsSent.get(master), equalTo(1)); + } +} diff --git a/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailReplicationTest.java b/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java similarity index 88% rename from java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailReplicationTest.java rename to java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java index 3777cff986a..835afedebab 100644 --- a/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailReplicationTest.java +++ b/java/com.sap.sse.mail.replication.testsupport/src/com/sap/sse/mail/replication/testsupport/AbstractMailServiceReplicationTest.java @@ -11,15 +11,15 @@ import com.sap.sse.mail.impl.MailServiceImpl; import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp; import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest; -public class AbstractMailReplicationTest extends AbstractServerWithSingleServiceReplicationTest { - public AbstractMailReplicationTest() { +public class AbstractMailServiceReplicationTest extends AbstractServerWithSingleServiceReplicationTest { + public AbstractMailServiceReplicationTest() { super(new MailServerReplicationTestSetUp()); } public static final Map numberOfMailsSent = new HashMap<>(); @Before - public void setUp() { + public void clearNumberOfMailsSent() { numberOfMailsSent.clear(); } diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java index 87faef2348c..804ab07da17 100644 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SecurityReplicationLeadingToEmailReplicationTest.java @@ -16,7 +16,7 @@ import org.osgi.util.tracker.ServiceTracker; import com.sap.sse.common.mail.MailException; import com.sap.sse.mail.MailService; import com.sap.sse.mail.impl.MailServiceImpl; -import com.sap.sse.mail.replication.testsupport.AbstractMailReplicationTest; +import com.sap.sse.mail.replication.testsupport.AbstractMailServiceReplicationTest; import com.sap.sse.replication.testsupport.AbstractServerWithMultipleServicesReplicationTest; import com.sap.sse.security.SecurityService; import com.sap.sse.security.impl.SecurityServiceImpl; @@ -29,21 +29,22 @@ import com.sap.sse.security.userstore.mongodb.UserStoreImpl; * @author Fredrik Teschke */ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractServerWithMultipleServicesReplicationTest { - private static final SecurityServerReplicationTestSetUp securitySetUp = new SecurityServerReplicationTestSetUp(); - private static final MailServerReplicationTestSetUp mailSetUp = new MailServerReplicationTestSetUp(); + private final SecurityServerReplicationTestSetUp securitySetUp = new SecurityServerReplicationTestSetUp(); + private final MailServerReplicationTestSetUp mailSetUp = new MailServerReplicationTestSetUp(); - private static MailServiceImpl masterMailService; - private static MailServiceImpl replicaMailService; + private MailServiceImpl masterMailService; + private MailServiceImpl replicaMailService; @Before @Override public void setUp() throws Exception { - masterMailService = AbstractMailReplicationTest.createMailCountingService(true); - replicaMailService = AbstractMailReplicationTest.createMailCountingService(false); + masterMailService = AbstractMailServiceReplicationTest.createMailCountingService(true); + replicaMailService = AbstractMailServiceReplicationTest.createMailCountingService(false); + AbstractMailServiceReplicationTest.numberOfMailsSent.clear(); super.setUp(); } - private static class SecurityServerReplicationTestSetUp extends + private class SecurityServerReplicationTestSetUp extends AbstractSecurityReplicationTest.SecurityServerReplicationTestSetUp { @Override protected SecurityServiceImpl createNewMaster() throws MalformedURLException, IOException, InterruptedException { @@ -65,7 +66,7 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe } } - private static class MailServerReplicationTestSetUp extends AbstractMailReplicationTest.MailServerReplicationTestSetUp { + private class MailServerReplicationTestSetUp extends AbstractMailServiceReplicationTest.MailServerReplicationTestSetUp { private static final int SERVLET_PORT = 9991; public MailServerReplicationTestSetUp() { @@ -95,6 +96,9 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe @Test public void triggerEmailSendByAddingUserOnMaster() throws UserManagementException, MailException, IllegalAccessException, InterruptedException { + //TODO IllegalStateExceptions thrown, probably because the two replication services per instance share the + //same message queue, but don't know about each other (unlike actual OSGi setup, where there is only + //one replication service per instance that nows all Replicables) SecurityService masterSecurityService = securitySetUp.getMaster(); final String username = "Ernie"; @@ -111,9 +115,9 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe Thread.sleep(3000); assertThat("mail was not sent on replica", - AbstractMailReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null)); + AbstractMailServiceReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null)); assertThat("mail was sent on master", - AbstractMailReplicationTest.numberOfMailsSent.get(masterMailService), equalTo(1)); + AbstractMailServiceReplicationTest.numberOfMailsSent.get(masterMailService), equalTo(1)); } @@ -141,8 +145,8 @@ public class SecurityReplicationLeadingToEmailReplicationTest extends AbstractSe Thread.sleep(3000); assertThat("mail was not sent on replica", - AbstractMailReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null)); + AbstractMailServiceReplicationTest.numberOfMailsSent.get(replicaMailService), equalTo(null)); assertThat("mail was sent on master", - AbstractMailReplicationTest.numberOfMailsSent.get(masterMailService), equalTo(1)); + AbstractMailServiceReplicationTest.numberOfMailsSent.get(masterMailService), equalTo(1)); } } From ebb47e2900fe18c7c3b5b5fec3d3b6b2a57f9cfe Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Fri, 13 Feb 2015 21:00:33 +0100 Subject: [PATCH 113/264] fix typo in pom --- java/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/pom.xml b/java/pom.xml index 6cc686d9d16..0ce55ac73db 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -18,7 +18,7 @@ com.sap.sse.shared.android com.sap.sse.mail com.sap.sse.mail.replication.test - com.sap.sse.mail.replicadtion.testsupport + com.sap.sse.mail.replication.testsupport com.sap.sse.mongodb com.sap.sse.gwt com.sap.sse.gwt.adminconsole From 52dfc60163d2af3830a5be00f1bb8c372d431821 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 23:15:20 +0100 Subject: [PATCH 114/264] moved RoIB dashboard images to correct folder, avoiding warnings in GWT 2.7 build and breaks in later releases --- .../dashboards/gwt/client}/images/compass.png | Bin .../gwt/client}/images/dashboardicon.png | Bin .../dashboards/gwt/client}/images/hourwatch.png | Bin .../dashboards/gwt/client}/images/leftarrow.png | Bin .../gwt/client}/images/leftarrow_disabled.png | Bin .../dashboards/gwt/client}/images/logo_sap.png | Bin .../dashboards/gwt/client}/images/logoess.png | Bin .../dashboards/gwt/client}/images/nointernet.png | Bin .../dashboards/gwt/client}/images/rightarrow.png | Bin .../gwt/client}/images/rightarrow_disabled.png | Bin .../dashboards/gwt/client}/images/settings.png | Bin .../dashboards/gwt/client}/images/watch.png | Bin .../dashboards/gwt/client}/images/windarrow.png | Bin .../images/flags\020ag.png" | Bin 0 -> 591 bytes java/com.sap.sailing.gwt.ui/images/flags`ae.png | Bin 0 -> 408 bytes java/com.sap.sailing.gwt.ui/images/flags€ai.png | Bin 0 -> 643 bytes java/com.sap.sailing.gwt.ui/images/flags€al.png | Bin 0 -> 600 bytes java/com.sap.sailing.gwt.ui/images/flags€am.png | Bin 0 -> 497 bytes java/com.sap.sailing.gwt.ui/images/flags€an.png | Bin 0 -> 488 bytes java/com.sap.sailing.gwt.ui/images/flags€ao.png | Bin 0 -> 428 bytes java/com.sap.sailing.gwt.ui/images/flags€ar.png | Bin 0 -> 506 bytes java/com.sap.sailing.gwt.ui/images/flags€as.png | Bin 0 -> 647 bytes java/com.sap.sailing.gwt.ui/images/flags€at.png | Bin 0 -> 403 bytes java/com.sap.sailing.gwt.ui/images/flags€au.png | Bin 0 -> 673 bytes java/com.sap.sailing.gwt.ui/images/flags€aw.png | Bin 0 -> 524 bytes java/com.sap.sailing.gwt.ui/images/flags€ax.png | Bin 0 -> 663 bytes java/com.sap.sailing.gwt.ui/images/flags€az.png | Bin 0 -> 589 bytes java/com.sap.sailing.gwt.ui/images/flags€ba.png | Bin 0 -> 593 bytes java/com.sap.sailing.gwt.ui/images/flags€bb.png | Bin 0 -> 585 bytes java/com.sap.sailing.gwt.ui/images/flags€bd.png | Bin 0 -> 504 bytes java/com.sap.sailing.gwt.ui/images/flags€be.png | Bin 0 -> 449 bytes java/com.sap.sailing.gwt.ui/images/flags€bf.png | Bin 0 -> 497 bytes java/com.sap.sailing.gwt.ui/images/flags€bg.png | Bin 0 -> 462 bytes java/com.sap.sailing.gwt.ui/images/flags€bh.png | Bin 0 -> 457 bytes java/com.sap.sailing.gwt.ui/images/flags€bi.png | Bin 0 -> 675 bytes java/com.sap.sailing.gwt.ui/images/flags€bj.png | Bin 0 -> 486 bytes java/com.sap.sailing.gwt.ui/images/flags€bm.png | Bin 0 -> 611 bytes java/com.sap.sailing.gwt.ui/images/flags€bn.png | Bin 0 -> 639 bytes java/com.sap.sailing.gwt.ui/images/flags€bo.png | Bin 0 -> 500 bytes java/com.sap.sailing.gwt.ui/images/flags€br.png | Bin 0 -> 593 bytes java/com.sap.sailing.gwt.ui/images/flags€bs.png | Bin 0 -> 526 bytes java/com.sap.sailing.gwt.ui/images/flags€bt.png | Bin 0 -> 631 bytes java/com.sap.sailing.gwt.ui/images/flags€bv.png | Bin 0 -> 512 bytes java/com.sap.sailing.gwt.ui/images/flags€bw.png | Bin 0 -> 443 bytes java/com.sap.sailing.gwt.ui/images/flags€by.png | Bin 0 -> 514 bytes java/com.sap.sailing.gwt.ui/images/flags€bz.png | Bin 0 -> 600 bytes java/com.sap.sailing.gwt.ui/images/flags€ca.png | Bin 0 -> 628 bytes .../images/flags€catalonia.png | Bin 0 -> 398 bytes java/com.sap.sailing.gwt.ui/images/flags€cc.png | Bin 0 -> 625 bytes java/com.sap.sailing.gwt.ui/images/flags€cd.png | Bin 0 -> 528 bytes java/com.sap.sailing.gwt.ui/images/flags€cf.png | Bin 0 -> 614 bytes java/com.sap.sailing.gwt.ui/images/flags€cg.png | Bin 0 -> 521 bytes java/com.sap.sailing.gwt.ui/images/flags€ch.png | Bin 0 -> 467 bytes java/com.sap.sailing.gwt.ui/images/flags€ci.png | Bin 0 -> 453 bytes java/com.sap.sailing.gwt.ui/images/flags€ck.png | Bin 0 -> 586 bytes java/com.sap.sailing.gwt.ui/images/flags€cl.png | Bin 0 -> 450 bytes java/com.sap.sailing.gwt.ui/images/flags€cm.png | Bin 0 -> 525 bytes java/com.sap.sailing.gwt.ui/images/flags€cn.png | Bin 0 -> 472 bytes java/com.sap.sailing.gwt.ui/images/flags€co.png | Bin 0 -> 483 bytes java/com.sap.sailing.gwt.ui/images/flags€cr.png | Bin 0 -> 477 bytes java/com.sap.sailing.gwt.ui/images/flags€cs.png | Bin 0 -> 439 bytes java/com.sap.sailing.gwt.ui/images/flags€cu.png | Bin 0 -> 563 bytes java/com.sap.sailing.gwt.ui/images/flags€cv.png | Bin 0 -> 529 bytes java/com.sap.sailing.gwt.ui/images/flags€cx.png | Bin 0 -> 608 bytes java/com.sap.sailing.gwt.ui/images/flags€cy.png | Bin 0 -> 428 bytes java/com.sap.sailing.gwt.ui/images/flags€cz.png | Bin 0 -> 476 bytes java/com.sap.sailing.gwt.ui/images/flags€de.png | Bin 0 -> 398 bytes java/com.sap.sailing.gwt.ui/images/flags€dj.png | Bin 0 -> 572 bytes java/com.sap.sailing.gwt.ui/images/flags€dk.png | Bin 0 -> 495 bytes java/com.sap.sailing.gwt.ui/images/flags€dm.png | Bin 0 -> 620 bytes java/com.sap.sailing.gwt.ui/images/flags€do.png | Bin 0 -> 508 bytes java/com.sap.sailing.gwt.ui/images/flags€dz.png | Bin 0 -> 582 bytes java/com.sap.sailing.gwt.ui/images/flags€ec.png | Bin 0 -> 500 bytes java/com.sap.sailing.gwt.ui/images/flags€ee.png | Bin 0 -> 429 bytes java/com.sap.sailing.gwt.ui/images/flags€eg.png | Bin 0 -> 465 bytes java/com.sap.sailing.gwt.ui/images/flags€eh.png | Bin 0 -> 508 bytes .../images/flags€england.png | Bin 0 -> 496 bytes java/com.sap.sailing.gwt.ui/images/flags€er.png | Bin 0 -> 653 bytes java/com.sap.sailing.gwt.ui/images/flags€es.png | Bin 0 -> 469 bytes java/com.sap.sailing.gwt.ui/images/flags€et.png | Bin 0 -> 592 bytes .../images/flags€europeanunion.png | Bin 0 -> 479 bytes java/com.sap.sailing.gwt.ui/images/flags€fam.png | Bin 0 -> 532 bytes java/com.sap.sailing.gwt.ui/images/flags€fi.png | Bin 0 -> 489 bytes java/com.sap.sailing.gwt.ui/images/flags€fj.png | Bin 0 -> 610 bytes java/com.sap.sailing.gwt.ui/images/flags€fk.png | Bin 0 -> 648 bytes .../images/flags€flagsRacecommittee/alpha.png | Bin 0 -> 497 bytes java/com.sap.sailing.gwt.ui/images/flags af.png | Bin 0 -> 604 bytes java/pom.xml | 8 ++++---- 88 files changed, 4 insertions(+), 4 deletions(-) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/compass.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/dashboardicon.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/hourwatch.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/leftarrow.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/leftarrow_disabled.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/logo_sap.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/logoess.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/nointernet.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/rightarrow.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/rightarrow_disabled.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/settings.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/watch.png (100%) rename java/com.sap.sailing.dashboards.gwt/src/main/resources/{ => com/sap/sailing/dashboards/gwt/client}/images/windarrow.png (100%) create mode 100644 "java/com.sap.sailing.gwt.ui/images/flags\020ag.png" create mode 100644 java/com.sap.sailing.gwt.ui/images/flags`ae.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ai.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€al.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€am.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€an.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ao.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ar.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€as.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€at.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€au.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€aw.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ax.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€az.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ba.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bb.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bd.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€be.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bf.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bg.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bh.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bi.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bj.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bm.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bn.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bo.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€br.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bs.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bt.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bv.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bw.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€by.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bz.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ca.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€catalonia.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cc.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cd.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cf.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cg.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ch.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ci.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ck.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cl.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cm.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cn.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€co.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cr.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cs.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cu.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cv.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cx.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cy.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cz.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€de.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€dj.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€dk.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€dm.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€do.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€dz.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ec.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ee.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€eg.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€eh.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€england.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€er.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€es.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€et.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€europeanunion.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€fam.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€fi.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€fj.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€fk.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags€flagsRacecommittee/alpha.png create mode 100644 java/com.sap.sailing.gwt.ui/images/flags af.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/compass.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/compass.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/compass.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/compass.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/dashboardicon.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/dashboardicon.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/dashboardicon.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/dashboardicon.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/hourwatch.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/hourwatch.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/hourwatch.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/hourwatch.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/leftarrow.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/leftarrow.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/leftarrow.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/leftarrow.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/leftarrow_disabled.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/leftarrow_disabled.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/leftarrow_disabled.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/leftarrow_disabled.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/logo_sap.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/logo_sap.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/logo_sap.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/logo_sap.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/logoess.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/logoess.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/logoess.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/logoess.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/nointernet.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/nointernet.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/nointernet.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/nointernet.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/rightarrow.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/rightarrow.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/rightarrow.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/rightarrow.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/rightarrow_disabled.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/rightarrow_disabled.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/rightarrow_disabled.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/rightarrow_disabled.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/settings.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/settings.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/settings.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/settings.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/watch.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/watch.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/watch.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/watch.png diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/images/windarrow.png b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/windarrow.png similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/resources/images/windarrow.png rename to java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/client/images/windarrow.png diff --git "a/java/com.sap.sailing.gwt.ui/images/flags\020ag.png" "b/java/com.sap.sailing.gwt.ui/images/flags\020ag.png" new file mode 100644 index 0000000000000000000000000000000000000000..556d5504dc28d89be22ec1883f12e8d8c07d5f41 GIT binary patch literal 591 zcmV-V0= z`}|7!`YQYT5C8xIh~*yx1DgQ1_jkSr_QgN{Gn)!7=zkRT=l(yS{y%^Jd$>%yrP!)vVq~xpZ~xA{{HjZ>hB|)Z~OQoBLBm{yCqA0{k;C`&+mV~|1$jj15^wU zKukakbkF}kzkmk({q_6zmUTZaUWBOraqa5!?(V-pgMR}R{04?0&`AIR#PSE^hrdi> z;*7t4gLVFSJ$J5DTibsorq@%ai5h7B2l^1G@*gM=fUW@uAjYc<48V{DX@ijvE(11_ dg@FMezyQzuBHsGv+i3s*002ovPDHLkV1jTADB=JB literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags`ae.png b/java/com.sap.sailing.gwt.ui/images/flags`ae.png new file mode 100644 index 0000000000000000000000000000000000000000..ef3a1ecfccdfe9cf7e9fc086a2c6c010f7977ed8 GIT binary patch literal 408 zcmV;J0cZY+P)@{}>pUYZ(~+fDw@M3(WY1!T<=sp}f-o0K!lJ3<>Jc!&VtMslE3ph2^H3FGe(F z4|^}slF1@l1Nxc}^5hjjU=0la|37)k@b@neJ^1|l@87?_{{z(l6@kcKe}Db@_Y267 z<&g#201!YdV6*>2R0EZ*bYA%%2Vi7me5m>mAb?naPGtnD20MY__n-f`0mzg8{s9CK z*hzmtW)s%H$oS{aAAkS?JLxaT`2W9u;n%DD1Q-B%#%TuGpFSS|0000ocPy#thD++(+#W%SS0Qvp^`1%0+_yGC)0QUL-|M&m^ z`T_tk0M7pe&iDc7fh@K74M*k&0{8f*88BG;;GY6ve?;&4{{Q>_{{8&^`u+g@|Ns5} z|C?Nv0*Gb#mu+n4z6ZW~#qjy_|6ji&ZeIEMk16oyzH)i@Pk$MNxLARR?a#kwpMS72 zF)}cG2M8dRUN*~81wCz%$A2$f`_IL|A|w6j^RHtc7{2`lYG-KkQ{(*knSqh%3kzr7 zs%s1kKL7#%F#yj00-^B^D;pD^^AqC)4C(?1m>@q26ceiU4*2{4`u_d~@vQ*ykp&A1 z3h$o${r~^~`vQo?Pm-bY@cm79zVQG5%l!B6)Riap%L`bGemnT<@2|iAfl64!%YWS~ z`p0YY_dig_cYpw5WYAmG*lPLk!;jy8|NZ#~MF0Q%{rCG1ko^Di`(Fhkd3F{?py0c9Vg{{gA~^#>F<|Nj5KapBR^@Bf~CXSi|t;lF=C z8-4)<5DUX^V9+rDCI3RuUy#}V{s46X!}R~}@4ug&fA|+@;n!b(1zCZ900KKMX({{sIILBZE*UQbc|SngJt$+`m9Kz!<;5 dBm+Qz0RWL%NC2%sdjkLf002ovPDHLkV1lARHI@JX literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€al.png b/java/com.sap.sailing.gwt.ui/images/flags€al.png new file mode 100644 index 0000000000000000000000000000000000000000..92354cb6e257be2cade71cb825027ce8d9efc06d GIT binary patch literal 600 zcmV-e0;m0nP)PbXFRCwBA zyv)hK@{fUmi2(>${xJOc&A`II@PmN?BKC`c;SU)710$d~Kmf4-r6niy16BY3`}g0! ze=L9h&Uybegp-Ns@87v^zGQMU|NZla@z1Y+zkdVKmFbHB0*HkH4F0@)&iwE1;&GRLuzkX(OGXMMa``@o$|Ni_2x(y(Jn3#TnHT?el|IhC;U%pHK`}6Df z@2@|9J^lXU=Z~KvfBzi*^!4A*U;lv`e*Fge3Lt=(82$q_`~m9z|ND;$!(WEqze~6o ze}X|JFO$H(U+VvX#{c{avi3L7@c;n?3L&6l7=Hix`}emN<6i~#|7+iUWBL8-&##}m zKKx5zXJr5LA80nvkwCM6egFs{po9Y?& zL6B;olm7ns#=rm&Kp-3b{RIXY(1{??Ha=$NKfkS+|9@us$Mfg+93j@fzkY#y2y!;i z&kO(o1PWH5>zPHwn7|>;0Mz;WPl13iP!Y(WKYy4&4hBXMSPugOKmaiw2gk!dq!|4J mj$lZE0R~eis zC&uN{cDQ8`!@ZNLOqwtoG6nhzPx}SV-d6yzFfcIz75{(rngJyDkKrFw8iaoThHCr$ zo8k9wMhS^O3=9AP!~%5B|9}4(ASQtkLN$m1CV&5705X362095KfLKJhW;OjWdGY7l zzrX*$DuLu*pcFC#NdEr)SL!$SY=)lz0mSm_*B_u)|AM4JP)(}33zy1Lc&_o7? zKL7y)bkd){fBym<_v`mxpbeRs`+WRVot-5A{r$IX$CJFg13u{3eX_{0R++j zR}GCkSy{&4zk!krKoTg(2n-k?g31B}5JW!NMq0d0000q< zM7LZb@rX?|r)2RP-+aO3(7q?c3+$-Vv0E)PxM3SDV%@s`#GkZvw_x^WBa~uq3^l}t zWdf(j(=(>^SgETc#5#EZT(4ObRkfxbzP9G;yza0;Ygc8-_*?EP(ca#`l6-Z6D0{tL zQ4|~MCSi!9Q9YkW=V$ix#EqZ!rc?eWA0TwdVID+3aqrYUXUhCI)Ad5!(cP!BhhW$Ayb2&r8kK!bz*2`~UE W(Ndrek0Jg50000NL7TW{{R0! zJP>i}(MpzaSkj z=eqvqa`^|6{{QbQRQ11q3~Vg_kG}Z}5I`(hw}ncX8D4$|h64jUK>or*=^r$dfI%n0 z$3MKz;!F0w91GjkkV%_X`@4 wKR^hm`X5LwD9*v?4;TUC6Bz$&3;+QJ0QLcJ=WT{TU;qFB07*qoM6N<$f>3bX%K!iX literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€as.png b/java/com.sap.sailing.gwt.ui/images/flags€as.png new file mode 100644 index 0000000000000000000000000000000000000000..32f30e4ce4eedd22d4f09c4f3a46c52dd064f113 GIT binary patch literal 647 zcmV;20(kw2P);Rmc0RI30 z|Nj5|{Q>>`0R8>{{QLs@`vM01^D6+%Ap+Y10Qw3Sr3ED)s=c}b05Jg0{{#dC0RR60 z)z$y}`~dv>0Q&p?0Q>ne0Khr|#}EMN1`-ttJvaFq6VTDg`OMJS*4F}vg#m0fQ1$QM z42u6QTCg3Kt8+Li1J#Y_zUd>4Lw{URe1tEjLbKmY(S0M7pd(g2qsJ0|z;{R`~d z`n<6E*3bb41p*Zr{{R344LkeqyaMyV01O!W0PZ0+TjlEQ0st`p&i?@b0Q{Gi0m{n% z`T6-B``1JQx+4kM0{rp_FfRy7P6Qq%_1|&<`|JPx`Uew8u)+%h2+ zdiAI7xt}_}e|$CmbI9$2>u h*D)|iF)#oG7yty8`q2#I8zBGy002ovPDHLkV1j5VEF}N{ literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€at.png b/java/com.sap.sailing.gwt.ui/images/flags€at.png new file mode 100644 index 0000000000000000000000000000000000000000..0f15f34f2883c4b4360fc871d7105309f1533282 GIT binary patch literal 403 zcmV;E0c`$>P)@|4`Xj5kLT%`al?B=W5I`&prl$WjwHQRjfmQ&G0jUOA z@&|Dug_Rm`2Y251|$~)1M2@@6mI}!8O6olw6y^Q z5X--d7nzS8`+x5q12kBmVFD!~j6c5_fMKno0(1^Q0I>i=is|LjXX40RRttS6cG0UZ?;7002ovPDHLkV1fxUnjZiF literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€au.png b/java/com.sap.sailing.gwt.ui/images/flags€au.png new file mode 100644 index 0000000000000000000000000000000000000000..a01389a745d51e16b01a9dc0a707572564a17625 GIT binary patch literal 673 zcmV;S0$%-zP)>fJ3En$GhGS>sbE%%m3$AD)q?8M9y>88-}kR7#RKlk!P~Y_PLuF7~U~3`~nC7 zF#yj00ZUDdpLsm{7ajP|&HwoK0Usg|6%f4L_{`Mi{rvv-`ukf=Ed&Gs-sA7L!Q7*a zj{*QO0M7pb%?Sw^g@yy{>ihEY{`vU@3=8@G0rvO$i3mOL`~mv-`W+b$Mmr&io5dg< z5v!7q0*L95jt`TzK8Kd(Utv)OSp_aLv){6ccV+Z`{Q2+asKUU&aO3`Kpz6wW8wp`< z28M3{0mSqnB#A*-c*8%1=RD#sSOwMznKA3=e&iEzwo{cA=PgXK`2OQ}gqId83!|%* zA_Kz@fB*n70M7pdECCwp4H&@R`1|(w-}M5x*74i)0}%fAt;XafA{48))#>Z>?CD#}*e}Ret0tl$#*RMZ7}Jl7Z|M45`5*URzH9L z{rmSnPy;{!u>dsyO%meg+00000NkvXX Hu0mjfN{&}S literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€aw.png b/java/com.sap.sailing.gwt.ui/images/flags€aw.png new file mode 100644 index 0000000000000000000000000000000000000000..a3579c2d621069c8128d7cf16440d5e45a3ab3cd GIT binary patch literal 524 zcmV+n0`vWeP)^}0h@W9fA9JOn#opKkr#VO<{LPm{QLg}EdH3Vb1Zzpp zqiZ+XNBm%5{`ViKi{bCTe}Dh~2a+HyfB%6Q|9}7eKVjxEfB<6Q<6r}-{{7;G=hB{; zLB+p+|Ni^;|DXT={s75;AoBO$f4@PDf8sps4FCQC1Q64wUw^h*hy4HZXO3U)@85qw zD*pci8U|DWQu_BVi2MUWKoOv;00M{w=;|*lY_o%kegGx^{`&*A{SSok_usF-P)oof z|A86+0*K|;FQAM6fB*FtoEI&bZG5mq328#as{}<$F zWDQ{Ffz1MH001phxCBNdC_2H6U$AI~GQbEJqg*Ti0R{kQ(yH?TRrCY^ O0000^@RCwBA zRL|V|7QjV)*ul;Rlceqc6W0{`~_8AeMg& z|LUXE8U8c=`}dziv6P|mEu%JjM}pqh@65md|Ni~w->=_)fBgZXe?Nc!ot?WGAOHX{ z0M7pe`uG9>{{ZXo`u+d?{`u+u=j#9V2j%Sc^797z|N8p+{rLO+`TP9&`u!FW2mk>3 z0#G;xDFDDQ2vg|)-@5Zw?JVTrAxNs~5&|AZ;5uWfQ`vUsMGm`>aGWoI7=P|oW>%~E z|Kk8dq@aG@HzdsB>fj@shYzX-a%=q<(frSYmfLIv5{HbzQ${~M_P_y6C&m>3uU0*DFd0+8#0#Gk)^|NLQBRAyCBeB9H+Wn{=Mr@*PJ_ZKV< zB>w@GFfafF5Ks>TBP0ZV{bBt5_ve*sKkwd`OiO#eX6=u=cfX!E#rX3NBT(umNC7Yu z0Ro8S7X#4ce?Xr63$phg)Bk64X8r&5``_O`AFf^jc>p5y?+^38KVKLa00M}`wU!~x z=I_1Fe}Df4D*yZI@1I|PfBgnRptt`0`2{iqMpRNPo|?TW>MPOFW(r1_!xlT-yep5|G*@e%^SFxf#DC32@n7=0M7pe00011 zNe=7o{FIuhzQ(sjF~t7>`T+s_0sj00{`>*}`~Uy^|Ni?^Z7Tcx|FW0O0tlpm;s5{T z+df{s^E1rDZN;v4VP>0|8GkY`{`&t1s2YfV{r~st=KUYCl59Xr00M{!Xv@F<41fPH zoWA^R$>xvtF5yd$xc~X{7o-}f=ig7DY9RXc``_>1K-c{N2q2&ahQEIq{`~z1RCDs; zw*~7zIJ!sAKj8J}&!7K)enC|K{|nUc|Mwq|27mwp*#K1f8;Jh=2byv8+}GKw-@AGz z%-rMu^XK3HKtBLA{Qvdq*I%FsKn(x^1k&&qY(LP_Uw?q)|3jz0PF?=a(?4bU?qHzN z5I+E=z&88?2q2IKpz*){`~&I%+VJc5-=Dw#?LYB#!lHLRL7BZ8-NZ5x*TXaKmf6TZ20qEQj`^F;~x-W z05U+*fBxNj@_W(7kJ(ujGnWgh$g%(Z2hso#K#U9wI~hQ+`3Dk_7z`j1tL#*2FTcgW f@C+EF009O7>dRn2w6d?H00000NkvXXu0mjfueTzu literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bb.png b/java/com.sap.sailing.gwt.ui/images/flags€bb.png new file mode 100644 index 0000000000000000000000000000000000000000..0df19c71d20d7fdc06e1cba01028983439b2bdae GIT binary patch literal 585 zcmV-P0=E5$P) zj51&U|NhG$_=oZTA7 z0mJ{l|Ns2{|Nr-Y#^3*c{=WU_!>_-;fvROCxPdkR1P~M0Xa=C_KR`47{sXG+pY!Yf zqu=)*{%)W0>;M0szkdJy_507SKYxDz`3KYh5I`&-cY{^``2$3Z|NsB`^ZWO@y}$qd z{r%_nFQBC$4Is{6hz5WF0@(mI8^j0N`~Tl>LzQ1Ye}a&q>MtPo*RQ`|OMu4x1!@2Y zAfSdne}TsT{`c$8-(P?J0c~)w`~?J`zx?#Dg*yp^z&88=2q2IKpt(TRKniTbum8-y z1bBWi0e#F0wgIH{FVJ|Pxj-8L0tgsNfByUdJMs7b-@h3^8h-!E3i%CU{QCW$1*GB6 ze~`2PfHeF82q2&aU>N+96yg32bmdP5238q{|Gydk0=52OVEN6!@<&OA7Z~7wL16>b z@DCt>7#SFn85mf=5eY;LZ#OUe_l@D-2Zn#Y82<4v{9|E|jkRQ8_`$&N0jL_N03g5s XX0T^_9W~6o00000NkvXXu0mjfXpthO literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bd.png b/java/com.sap.sailing.gwt.ui/images/flags€bd.png new file mode 100644 index 0000000000000000000000000000000000000000..076a8bf87c0cedcce47099c6b74b59f2c9d1dbce GIT binary patch literal 504 zcmVCcfPV}OzZe+)fYC1)`hmpw%>WR9L@~Dk00x0r!@5ksR;+hV0>ZLfIcBS@orh;x zv}95za5W)x_7^}bV3YqpdH?h;NZnuSC%?V#{+0gl`~RQckJx{&l>NO~;@7XgzkdJz zEh{VwR0LSuiyWGR{aKQ z00-R5+27mwp+3@G@Um)kt zzdstU{yWl9SIB_0ft3II3)BD*KrA2|{``^Tm;4PDy`=a1tTre>fEI#8 u{{2z<2NVVaum*qtVq}m+iAaI~Aiw~?reC_kVQV=60000gsB+3J~Dn`pdxgmx19A{hgns>oXkhsL8>j&wfIu2Rsv+R_ zAB5T1GyoL?1Q1BWpFe+p|Nf1n;TJ-~Kd@?uhChFRHUI<=#0HS!U%!8$YJib28yLY( z0tg_G2B7i3e*FSj@aq@44gY_`jRywQ69xu=0D?FP=vyd&x*M*V6|CV8P{Uu4OBfgc z0tjNmA4y3`us8lPG6H?}8%zRafgb(Cp!5f*6oP{xAq} rF#LPR@IoIX4%E%Yz;K6w0U*EtuUcjA`-_J300000NkvXXu0mjf+>pV; literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bf.png b/java/com.sap.sailing.gwt.ui/images/flags€bf.png new file mode 100644 index 0000000000000000000000000000000000000000..ab5ce8fe1237a18d6809a5570024eb108cb14a3e GIT binary patch literal 497 zcmV@|4`Xj5kLT%`al?B=W5I`(ov;U*021-Lr1*!W5(gW7O z@cTDV13&<=05Ky_HBiN$KMcQt819e>gHP`g_|Np;&&;0(C^$RHS>olZ*Q41X9v27}ar6#W9}0h{^{OajIK0?h(y z_zOe~zZd`lhy{p$|NA4!EeST}?;nUxkP49VfMx;1;V%&U|N9rH;SU2q05LM8GXV8K n(H~UyA0);fa6B_H00bBSQ>$p&WfB*UmB!B(-`wPhZ^#?@C^U5EbbPyl_g=3Hc01Sh${{P9=HB%4| z34*<3-m=C?^;L%miR{xQv2*hU;8+Y&0Dxf-W~KjsusWFAC4@j0#9_j;X5z6SjRhH> zd}sd(7FPhVFdSi!*Zj@;_Sc`kfByUdk|3A-`STmZ_yb~qxIp9o{E-p)bLt=iKmf7) z+|Ix(`{&<1pm88mK&}8g1WG~}e||Ik`OB#Cw~m1UAb?na(Zlrn4}+v6P!1@{@CT$5 z&-#|8y{IY2iKmf5omHz(^qAp)ym^JhN+jk7Vet{H$(Z8=>|AV=V zva&!M00M{!p&AHub^kXuF^G#pRQ&(-8^nMkpk@XJfB<6o_wOIv5C1P;X6Wwu|KUB< zc%X*g2-OTg#J~U$KumBafq;(A|A`a+OH08N{X($;=pP0KfB<5Fs|GR7oM!0i{{QYB zSm|$=!=N@Wf}I2qKuka<{f7YtBjf)QCj1u{`~UM7nrdKh0c`*VF+c!8{Q&j%sZ;-Z zd;dRshENS5{{S`og~Sg)05QIL^-54s5b7kL(8GuS8yfz9{>%Ui`+q-vfW*P*^_43C z0mK4S`s2qBh&}(NP5W0_$$%QQ@R)`GfB*vkck5Jby^kNv00000NkvXXu0mjfy0pt~ literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bi.png b/java/com.sap.sailing.gwt.ui/images/flags€bi.png new file mode 100644 index 0000000000000000000000000000000000000000..5cc2e30cfc47452d5bef949628e955a522d59e50 GIT binary patch literal 675 zcmV;U0$lxxP)$wbBBJ zMTk#;k4<3e#ePFE;{?TciOx=-NpIdg`t|#dcz^$YFqm+of7AU<7UGr+0t^5F#PsJM z(2)${Jd)SmT+KY5@!|h>po)L5-!r~>3uJux|0`&Jz~QGy6a*CiGyVtq1|Wc#zWw|0 z_y6C2|NnAvaR2)A>-NX%|3Ci{R?!hvH~jzU``ypCK7Rkq&&~h)FDNvCApj5nF#yj0 z0_X_*{{Q{_|NQXv@Av-qBMmR$teo!O*6-%u-J+8r3n~2s`s?uP{Qmp>{`~+900M{! zNC9;}eE(3IS9-C{62AZc<>Jx>%af}0@?r&05Jg0 z{{#T;0Os`NWh`ko4>SM%|Kw(60099Kad7|t0qcf^`tR-^sIP`Fhru7lkI#w#>Hq?W zkzqQ+@08y_|9v}t;{CEE%)%nTc=-MC@P)7S@!~g$((Ud|+28Q224FCZIGXF0FQ1zcbfB*dX_lJRr z;s3wCNa|2D02KoSAbE^!0Dxf-mZ69ApkCb5fP^9ydGO$cm6w`kGj(t#`M{tFlLo%j z*4%mm2&CaJSn02SzkrtfWBLcO;r%bLy5Gnoqrh)qC;VKmZT`F#yj00W%wG zw4zx17##T6*Z=hN0Tvey7!|wG?K;V)0{r{_`}_X<{Qmp<|NQ*_`}@YSvI2-@&cAgG z*R?p0?_>V(@&EVlEP?{x|1esA`||VNU68t8zyJRJ_3zg&hTp&0WMzR?0R#}s1|E|% zRV}tRkH6o2#3;(nsVMXI-pgBmh17ms`TzTu!pfBl%*_9R;OEbWDJei#0R#}sGpDd$ z96Spi@~bn7G5z{~^_!ZLy2#s;5B~o8&G`G*|6f0Ap0WS=_rFd2C(ubi4FCZEF#yj0 z1a!1=2e0}MwgDi3SU@2S4mePl{012eQ4J-3 z|Mm`933BS6zyJRJ1;ztF0D(08|NVzmQWB*4&)@%$um=Y`lmy1W-@hDx{{qQh3=9AP x#JH7#;TJ3-|G=X54_FW_j)5c_0|P*S0RT0dOSD~(4;cUe002ovPDHLkV1hfJDkcB` literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bn.png b/java/com.sap.sailing.gwt.ui/images/flags€bn.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb09849e9b5712e9cdd8a2c25035da201535cf5 GIT binary patch literal 639 zcmV-_0)YLAP)8n_8%|dG+hlCr-g% z|9<~wU<9iE{r}%DMphZ1Jpcj3%*DZBWo-@72DFWlk(-gj@!ngb-$#D&C_i9e{QvLY zw|{?r|NHlgiHY~?^IHG`05Jg0{{(k=dF}7->+9=cXlNG}79t%T{rC3_t(pA#`tSX# z4jKOh|NsB|;rr`({{a5__y7Wk5omBxQSrlv5B>nb??0VZ8qCZrpS+?}gatn>o%-+B zPl=+||6st##PspQ2Y>)#{Cu5(QJ~_^?faD-pMV;E|7MwLubjQ@B-gLso$mVYgECpU zxpfp2fEt*XnBKpC4-fz`0M7pb@zVeZ8g>5r-~k2r`Sbhd;{WmN02I(80QmHEv)Ik{ z>H7Nm`}_MdF)?FhWi>T50*LYBMFw`s0-y$le?J(QfB$Fr_2c8e-`_!j`0PG|ckZ*_ zzkY$-`sc5Tips@{7Xbo@1sGC5IYwrQfB%2{|MTZR!ygX*-%McG4cZmTY ZzyK0(F$K@T-Dv;-002ovPDHLkV1la=J3;^e literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bo.png b/java/com.sap.sailing.gwt.ui/images/flags€bo.png new file mode 100644 index 0000000000000000000000000000000000000000..ce7ba522aa7e948d581478432643c230eed1a658 GIT binary patch literal 500 zcmV^3LvnC{|x`%yaFl$ss8ha0V@6XKS=c-5cwO(_{}IL0ki=i zfLOjo{AE-9^-mn=h(G`Tfz|!{{r?|W+uz?{^yeR#!Nm9NlRN`J0I_^YV&Ikj@%Q)F z|Ns9m{0FN3^Z!3k!*8HnFvf4N5xas50L!z?-wXsAPk7$ ze<1l+iQ((fPYeJ7#KQ200VpKFA^{3Nph^G!{9^b86oDW}(Ek1R7ZiL9e}Ret0*H}e qBRB&8AR!3%7c6cef(%d+Aiw~vBYd|xMihYn0000j-HAXl7XJGjM{~r+i{r~sxzrQwG-&h#_Y&p;L z=ieV7_s^f-zyJOD{rC5(+EoAn1k&)I;s3Kw&;LT?{{2}P{Pq8T&j0^^J4?LvUd;UK z&+k8ffB*XXONL(tXahh1fi?X94^$0Q>Z$uRRO)Y4)uapmB!2(-ul(c1=C+V!kAF`) z$PCo;`_FHns{jIs3Fu;wy-Z(c2YwAz{&V4aXk?bejFO*Op&u`@i~PecqBpL@DIoojb8mb+WHGK%fG2>)a3=iVf_~v+`s?*1;!`?Kmai^I3i*ZIYtpN f{g(k500bBS@Ns{bTs^2gCr95H^s6Fo1}O2_S$Nfl}4;<}o-r{^R8Q_xtzX z|Nnu?|Ni^;2gm@D3=F@4EC#P1& zzuV{ie)#0qf2Lnh)j(~3VeG$ufPMl9Aczeh3x56n#l!R`HRQLfpFjVwfkN;%I39k( zVgne^e?jDThF>oL0*Hk{T6(^^8pGqq44*zhqZ1OZzkZ>_F(mFeH~<0+0A4S6=>Lb* QN&o-=07*qoM6N<$f&=*Yr2qf` literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bt.png b/java/com.sap.sailing.gwt.ui/images/flags€bt.png new file mode 100644 index 0000000000000000000000000000000000000000..1d512dfff42db1ea3e7c59fa7dd69319e789ee12 GIT binary patch literal 631 zcmV--0*L*IP)?&DC3JV7l?ccjcgB=Qq4l4w|Q;eOZ4z|IjsEI`#PYSloM|AFHF zfy)2>XZQ=$@t;lU|F1tRzpnmg{PUOLH{-8A|F{4CvUU?d0I>k|0TuuM^_1cNUy$nm zzZn?*{^!*ACzSEy$NyhHzB0Wn`TXRU=!%=n%Ci4h82|!^32gR%glZtk^6USnb00o@ z{`LDW>%U)&BK_|-y-75RkKVi&O=|xC<=6keK+72b0*D2q;Xlai|Ns9mF#h@fM(h;aV>^!x1-zNu02XV-n@ zm3imZ@&EI0hChG)|NZlafdL?Zn1I0yH26P<=DUx29QQsc%}izF;QV-S-hYOl|1V4z zX|a-<=Kf#g*Z)7i{{y|szyJ_HEKEEM|C#>$KmJzoZa)M2-~Wm-e7Cl+vi)L|kY`}} z&A$F20RV*|BkB*O Rz6SsR002ovPDHLkV1m;fPLlut literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bv.png b/java/com.sap.sailing.gwt.ui/images/flags€bv.png new file mode 100644 index 0000000000000000000000000000000000000000..160b6b5b79db15e623fa55e5774e5d160b933180 GIT binary patch literal 512 zcmV+b0{{JqP)O=a{vExMP2%`MCSoB^FIcLe_%lf;|~%E5I`(IQNh}3Ao>6Q|DFUXMn*>AqQd`w z|1kXd^B;tM|Njjl{{hM0zwd6?1Q0+hV1xeud-4=Wy?p-%sO`^#2S61Jzk!N?s)6X& zzhA%p|N6}=D+{y%Ab`Lc{sL9~1=0UN4*CD*7s%9KAf+JHKs~=eB-8KTKvw|-5R1&; zzd&a|ob(5%^Z$Q=wHy9p13+aOpFRNu5F>N&`Tk_-7w>=n{RejQzkfh&Kn{rf10?_b z{tFTZibx5v&dxav5I~H7|Ney-|DWN1$%1FyagzUW0464;_wU~W1Q5$TW@eGxtUvee z3vAf*8|igK9~@*rr66bh|NrkNM8z+V zAV?>O@ek;bKfu6d00k6 zPW&$KB`I@TtA?2x@Q~7pdcWi#1>DDZ2>MuG0I@IumHuaV^&7|soAMvT0IK){RtF@1 zgH;3B;_Qq-34j1%{P^)BFE1~|NkA(gBv!!4$aw$$eSiRB1ga=2D|__l(H|iA4T2Ex z>lc`SQ9x2&UjE?0g8%`<0`lvhzd$}*14J4{IhY2@0~G@V5J82%q9;EBlXu0U&@_82I`BfBC|Yn~P8llm?myQ3_K18=`^X_ivyEfB<6p_xd%%#*GZ! z-C&_V5IGPDK|g;X*gypg3;+Sd!XPTj0Q7fK5>&$Y{sHB| zjK9DB00sa3QR4q|tc?L6fSCSm+3>HW`NxbYpP7GsX8sK%KS9yYPfWi)GJ)AFzdp14 z{>t>1fsFwmfLQ*Ci!yxq#8guv=_mQ;-(Rp{AoSPbXFRCwBA z{KboB21 z`ppP*BG3|`!vO*asNvt=e_;3gWsp&PA@E<2jYs6ilRy6-zmSku5?~OOP=5aV@4sI_ zSN#QP00zQ!JD>SpAKYgR_QStF|9~F;2M|Ck zz%U1e+FyoWe;B_0VVclq;}vp_kNy3hKMWrj7*{kqJbMcaQ;_Pve;I)x1Q0+h46j}> zeiN4E1lkA+>|e|*mzJsW^M3)l^W_^hf&c&c`Mv->`48yHFF-%s0|+3-*GLfwiRNz% m3}3(u4lrUsie?}H2rvLBbQv(L;??y40000e`b1WGV} ze#gbd^&bp=ef-GykAYc$@BhDltSqdeSy?RXJpcawy+5fJAb?mHcsPJ&{d@EF@4HtJ z)qno||1xd*zaM|Ot4f#|8A1AQ-~Pubz$7IBv;-i4SQvovfB*ge{fFVtAE4aNE7yPi z`^Eg=5yP(^Ur(Q5`TLjM%#`8#_rHuB|9}1hItd_vSpNO`#qjs<|KGp=|NIHm@MXqK z79K7}ZvOwQ9Df%s{`~lX*xI%KzW-!k{Lc96Cr|@G0I@Lq`0?-epMSr9|NHeD?&umBG}6iH(DSogJte zDERNk_kV0Zng09%Itd_vSb&=T{rt@+B*66dJ0my{fpNkvBm!jpV_;xnVgd@XDk?Dx zh=IJyzyJ_HjJJUrz)|>#fr0nRl^;KTz#|yK`2G9$BS%LzureS32rvK^u`=B;c)+&+ O0000d94tALrX!Wj0S6V-v9sr07*qoM6N<$f?*uARsaA1 literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cc.png b/java/com.sap.sailing.gwt.ui/images/flags€cc.png new file mode 100644 index 0000000000000000000000000000000000000000..aed3d3b4e4467c33717ab3e2f61596e06113f9bb GIT binary patch literal 625 zcmV-%0*?KOP)Vbu`-)NXk-Y`p?2$B#e1 z{(buQ`}e;;zyJNd_UF(2w_nb@VUiUP2igD-Kuin_BL6vl`zgIQV*mR8|8G|2U!T7I zn`-)Ow$bxne}9LGfB5z5*XcJ*K(;aeSD>o^0st`p&i@1e@&c~g4N@7~{}B9V!5sbp z`j|K8<@Nq-&kt=P>@pAZ`T7Q#;RiPl^j;kSgdzX}05Jg0{{#R8=f>y-z~LG0`u_d< z{QLg=2?6_k)DH^)`=sFo8YTUi-v;{r`o8J{6Wj>^)c^vB2^cJY|Nnmc_5b@He}DY@ z_51JdufKoSslWR1>-W9)3`r;XfsXn0`_Fc($H!lT!V@5Xn1Igu{qOIuf50&L{qg7D z?|*(LofMGa_%_k-*{}b_0g?9mMSwvE)Bq4bEIr*1})*zF_+U)&LMdEMOb{Nb-vOh6FrNC(tkd{&4;N%g_D$&)>g> z0^hO}m?z%iIsOJ1mka;_#K>R{i_RZNQ42x8VJSe8f#Ern2@qfa%&RHCyvg>Q00000 LNkvXXu0mjf=TkSf literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cd.png b/java/com.sap.sailing.gwt.ui/images/flags€cd.png new file mode 100644 index 0000000000000000000000000000000000000000..5e489424884d2ec9e429f70d69af00edf242a077 GIT binary patch literal 528 zcmV+r0`L8aP)JkPqeYl28iLgD=0{><0$P44T5yOrT$dE?(KkwMFdoG^-J zGv9P)Kk|i5`lcNgUUAbboca5{hI)v&h!9!~`Yg)Ld}$VwYqqXn@gVLi>3LSVGm1W? z3qnDJAk6chH(u7f~FohUBCxfQDx8?5BQsCcprAnfVhO SHC~zk0000@|NZ~|iEi8i zh~?j3h8^dV0y&$fY|i?}`2XK;hJSzm|G$6#|L@;V)&KqZ3sU;$?>`_ZF38C6g#jRd z7@s_ODJ3Nev=9gw8UKSQhX4P)z5oCE{eQ>y|1j|H-+x9%#`EXT0R#{W1H*5i5@u$g zs{agN2m-%;fixfipeaCQpFe*F2q2cU!)f`}`5%6M06O*WzrTN({{8*;CkCkD_wPBk z=llWket~@P=ieVecEOTuB>(}$vTge|RYlbYUmpDW_v_E!KfnL|{{8n?G|>28zowm@ z_UrGj-+zAtrGTUoztWctUjPD#h2amw@BhDn5OHz(52cFPg zot=T34qlT?57Mzkg5?0ssOG0Ftp>paW8OyZ`_I07*qoM6N<$f+2k} AjsO4v literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cg.png b/java/com.sap.sailing.gwt.ui/images/flags€cg.png new file mode 100644 index 0000000000000000000000000000000000000000..a859792ef32a02b41503b5ab5f216191af397e02 GIT binary patch literal 521 zcmV+k0`~ohP)i@P`2b{=v9^Ktp0uZ001fLMSM)r!?%mH+B91 z=ii?+(ckX8zrd=&8g~5t-|*}I50LQ;KzRlRfB=Hn08;wv->)Q*Kc0LLgTdsU-~VfV z{r~#=|1XdRMzE6r0tlo5X#B6gzhXrG`18Y*0!g6iieLY~Kvgqz}`W zLWO>Z3Lv@r7+CdZpiy8Ae}EeP0z(_<2Y>)#Vfe-1#`njMUlORAfdMG=o8iiD#%aG9 z#Q!h|feZ$rzkeYJ=p=vuV)VVru zEbxddW?+yz2*QkqGed%bf-LEdzK#qG8~eHcB(ehehXZ^X_BJxLg#P@)J?{042oSeSe+Wur< z_>-3Q%f;oFz5UOhKY#rA@%{VvZb^+n3& ztpbN0X6XF;skA}xPTBI?pa0Yu6bOX)nsXTH-p(}r%l>H!zi-^*0%xF$7(8A5T-G@y GGywpKXU=^9 literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€ci.png b/java/com.sap.sailing.gwt.ui/images/flags€ci.png new file mode 100644 index 0000000000000000000000000000000000000000..3f2c62eb4d7dc192036af889b593b782dbe8abac GIT binary patch literal 453 zcmV;$0XqJPP)^Az=(+fAb?mHzA!MjRs*H~|NF=I z{~td;@BjZG-hUVsqZ#w(|L@=b|NQ>{d(*K^00G3pzycQk`jp|{UxvSb;p*Vv_V?Sr z{{Q;@@3$6WKrBG#F*5vR z`2XiW&+Xo6{KsbP_-SF+DJF`pL$*;0gaz z7NKVhyo`U;4*0+SK#>rfFFfafF5DUYXKh;r+@n*scPQUy6`!63S zGXn!71H*qtMn-04W+o=4|487=moEST#KiEAA;m(3;Xgy1iO{3BKc9a12~-UM7^Z`) z1qdLZI~bN-c=z}J|D88K{QmR*&tHcBxB*ZDKmf5Ya0q<-!`is-{h$8~f7m(x{QZSX zH3Jv`1P}|uy*o^wIGI2G`1kie)4zYe_&EQ6|NftW0ohon1}3I|Z{7d|5aVBMp0ssVo0Ek~;W>Z#0SJsA+2j`G% zv|UVeYYs-#Sn6_J90h1VosR?LBU7{U1rQ6+R0f9sPrg3=`~UA>#=n0a|7Q67_y6zT zP{j57|G!`V{{zu)Mn+knB>(|98e;E17s>tTLaMG-~Y>h{r`!g0jL-tfS4HmF#P%V_xIo5zyJLP zk|2W%e*G8w^1X|KC618W{eAoCFX+EI literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cn.png b/java/com.sap.sailing.gwt.ui/images/flags€cn.png new file mode 100644 index 0000000000000000000000000000000000000000..89144146219e6fbec7eaa89e1bf4b073d299569e GIT binary patch literal 472 zcmV;}0Vn>6P)$bmtfBa_T{rmsVufH!rO2O)W0!b+P{TrwO zAb?mv&i(NBbu;G`sX*)cv$d*%>MiL-(QdhpkjakVmyu%k;sfcNRj;yhJaxT5MTg0u5&QfH8#Bf O0000XbC_7v4G9~kE;3? z$h?1GrT>4y7{3|*{r>Zd0U&@_82$jY{AFPH^Plk#hy=QU5o|Ds0oDLyFn~2M0QE2c z1P~*G;PKK9g@-SH{rUU<_aC4k|G@~v`1Kn|{`&=C|M{yZ!G37cNq_)iVfexD=MNA8 zh5p0Hf4_c1v;p<}|Ak}_F!%rh2&CcXZ-(!`82&@8$XA^gF6?xq}zd#y5N`WeVBbyB+frxZH0- literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cs.png b/java/com.sap.sailing.gwt.ui/images/flags€cs.png new file mode 100644 index 0000000000000000000000000000000000000000..8254790ca72f98d9e79d94bdfcb8839b1fd434ad GIT binary patch literal 439 zcmV;o0Z9IdP)|s3jQ%L{DG0k=ogG+0SLgMnA-pV!aytqt1(q3VU5g2cTadE%W~lnu`}m; z-504zOD10s2K8+~RqF^K7O;8$o;>*sR0>r0`!|sM`x~eNg#P~i_4n8BzrTI~MgGdl z@-i@d0|+1%Q&SyHO(9WHu7Cgk{{@--A8b6x2B1zb1BygNIks=N00WJ{7y^0)%zz1kWJN^&bany+5X-;o*O`wU`+x5q12BMq=+`e0f-vAB ze||Ik`OBc90(3J#0I>iAmFf2%21!Yv97q%>_xm>^i2a8FLV}HlAfRG^0AlbRVNjfdgKH->)<41fMG{Q1Z53yA)}NHF>VM86o=*Z=~EMTLRklg;&|8vY-+ zh5rBh|L@=bKM?ZoKOp1JpMQV<{rmmr|L=eQfFkpA_5cJB<7Wm2?&_*f&z|!oCH%Iq zVPN&@1^xa7gnxhi`TP6#zh6KNfByZH;9_972M|CY z4S#m*VEXjw_teQhfB)LctFzlGTKpda#8eRa1F`|=V+j!s1_l{`0AhU0z`&K2_5c3; z?|=UACdB;@3Hy%?Ffai9!1(6P8-M@;JL%7#4}bn}Wu*W1^8Nqk|9@zxfJk8Yg2Rge z5=!5G`~e6c79IwMkAMHSMfCl8%kXF6O^EHl82Alg`~}6s-@pI={`>#;Kf}J}IRF8~ zc$$I1RCFdZQi0L={}0epF!T#d{sWT?V3G+SzyRi{Q|!NXWpDrh002ovPDHLkV1m+z B7Bv6> literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cv.png b/java/com.sap.sailing.gwt.ui/images/flags€cv.png new file mode 100644 index 0000000000000000000000000000000000000000..a63f7eaf63c028615b2ded5878b5e14a7dbe962f GIT binary patch literal 529 zcmV+s0`C2ZP)*82p^=00=`tlmIeOf&y&dk@6oT z&CV8YCOfMzZ7b-;WHc4ffO0*K`UGc#9pHKW{n=1`s=Y4`sB0UGuD|F2(+4xFrs{6A+r|Fh*S$P>SRGs(%l z+O-QHfEa)O{tdAK=ovi517(5A-n@AO5I`)f0sl(Vm><0RZzq2;(QtLuuFT6X6;y@p zb-8u)9gSFj-3}5^z@SqUX4qfA01!aT3<}j|#!Osn3@^V(zxysO&hx#~ZN`f)vS*$d zpLlA?$HBnM&cM&k$j1o^^oKjY0t65vgZ)>ehy*ei5K#*ZyWju7F%Ll?01#jRixNU5 T4U6zw00000NkvXXu0mjf>cZT~ literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cx.png b/java/com.sap.sailing.gwt.ui/images/flags€cx.png new file mode 100644 index 0000000000000000000000000000000000000000..48e31adbf4cc0074f40e95f87c1f103b91fe270e GIT binary patch literal 608 zcmV-m0-ybfP)Uz~~Q*L}q{yKmai^Fs!d`Rbj9@@=xl~KZbvQ|NZ^*|L@=bfBpc` zKOp-38^rz{DEepVv)`L`Yyt=%76t}3hHqR?<~aN{I`!9n&u`}MKs~?z|Ni|Cihlk3 z1w?Q7fI4M?HUI<=3&<7!82Y{{Q;@?^mDN?|=V) z{r~^ll=rtu5CcE}F#**BRkyY(adR>Mc_H{~T5^XRV~oVFSgBv#s=t2z{grn0SJIVV z|Ni~D^5u8)5(a<(0%_pmV<{@S{pr)+Nt0E6|KyV7{&xHKulY}Z0S*2Ic2cIyuhSoY z&3*a@=p=vu0%~ALNs$10lZEA9e*W!WzkWV{%9A7hYlh}ewm<)V{rd@2{rBIm&Idr} z{RV0P2mmnv&i?}d08{q%2kh+oUtbt0DGA2L@2{yn^Xv%Q%_4hjZ2Q{KfBuh*0fWjOcoPYi>{Qk`ejtlm`TPy!Q@c0|K1n4Ay00P>u ul!4(JQe+}W>@P40kp+Sq42=5$0t^6?P(4CrvcmZQ0000s1`2Y9I{~ve$KWO@Yukru?KTxgz{s9CK3*7Y} zZC_6Qf4TJkuQ&gHzW)Di+kXz$|9k=eg(Cm|XMz~|=g%L20Ahjo4`KiVr|y5|U;qEV z`p@?NKb!P_9;^R?@o<+hfSm*oKp+h;f&YIQ*bg)L>I;A4WVP82*P-nR{;Wu@%{Vvyu7@~4txIf`R>OD+-01E zIfRil07L2S-Mat*#Q65@TRuKMWMi;}EJy%|ff@h;2;_%%@7_UT@edf{0+7H22rvMY Ws9gjvbyTka0000? z0048MLcfb{@Lpld*gfdL?Z zSQvhRtN^J#x6%GQNHxSfxHgc;AD{-1tAIKH0tl!9q}uD*5!1Kl8Kk6va!f$;fJ%WL z`2Cv^NdEc54D$xi27mx!VfgXqT8ME7!~2)OPy-`SXu#NiAkhzFFflLy1Q-A_8F>@M S6G{sJ0000MM3s^~RU@ue&pds5d;g%WqHa}IsktuK5$l>zi01D0l1`Yj zY=(GjEg1}*aO540m-3+Pqh`GN`rbrZcA_w*&^nS}3_aGO)yg)dy_SyqXLYpS>AUqjV{G|yhq<0;nps*=z7?mmLwpBW1MKDL sY;(=&#;M1& literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€dj.png b/java/com.sap.sailing.gwt.ui/images/flags€dj.png new file mode 100644 index 0000000000000000000000000000000000000000..582af364f8a9cb680628beae33cc9a2dbe0559f4 GIT binary patch literal 572 zcmV-C0>k}@P);we;9uK`S<6~zaM{qBL9B<0yBR7V`E_e2q4D) z|Nnpa!Ep64!{fLA8NdLj;oraifB*mg`;Xx-kn#6Fhzn-q&in!pKrBENJRA&WD*ySo z7*5@0`26EP69WTC_22)0z>5C-g{l_hVFa245I`UeKudt6h7^Mc@BgDW81BCO4-x~L z`sXhc{R3+I%fRsKA3y*x{R6rHsA1>M|6jifb2E4w{z{xB{`~z5CPC;Y7bk|9<`c2PXgjR$^CT@Hzz$KrBEfF#?VC z^aSdB^z+g5SJMxCJOGqNsQwQkfr0#&=?~CJ009Kjz|71H^!MIRd#Ajb^76;aUyQ$y z%m(TN#spBq-#`C>zGeUjAdrR+|30kwu=eoBL!3-pGMq9%bs!`E|ACMovw

    4;Zk2 z8GbPU1Q5%#7t@Nb6*GKbU;u{yA29j{CVzn$|6qa)V3LCYAiw~8(_SNKujRx50000< KMNUMnLSTY(1rd4x literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€dk.png b/java/com.sap.sailing.gwt.ui/images/flags€dk.png new file mode 100644 index 0000000000000000000000000000000000000000..e2993d3c59ae78855f777c158a6aae6c1fb5c843 GIT binary patch literal 495 zcmVh!ZNvLM`<}kPiIA3?K?Zl!VJuS0ABN12uI2v;s z000mK68GQM4oDR3?|C6;zBc4LR82Q1eETXSa+3nD0Ad8%4|Ml`Fn}2U{~ypshW{9V zk%{T!hYtV&#KHiVV*o?2zW>+&Bgm+K00G4EikX==E9>w5`yf~S`o*<00G4K6dZ++hy)_Bw{QPEdi2K7 l5H1Kw2asrHVqgFWFaQRwS@oh;XP^K8002ovPDHLkV1foV*8Tth literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€dm.png b/java/com.sap.sailing.gwt.ui/images/flags€dm.png new file mode 100644 index 0000000000000000000000000000000000000000..5fbffcba3cb0f20016c9717614127b89db4c9664 GIT binary patch literal 620 zcmV-y0+aoTP)yt}{r~U(pZ|aU z{rk@#!dhJXj+vP`D&$&!2yPR?Jud5I`&tqo00z_V3?cpb$_)%#~li z5Bw?p&G@T5&5h~tB<;UIJ-`3_mgbQL+5iwhOdtm^{QZlh+Tg>NcGo{qR#UR9ewx4g zlzHy^uRp(j{rmOj?;oHBfB*n70M7pb{ow!s5r+W$=Kufw0RQ~_o$a1N^GPoTT-yiw z&XGVBSbe$r0&xrf|M~#~9P-zx0st`p&i?@b004Y@cH`sX`~3X;`}>j`1PlZ1WGkKd zF1~FN+w&9T}cJeJULy4V8r?0tNHM6WN&<2126ppbC05A-~q$vMCOd&N)3^rn&3WaiZo>@dB zxpL5=L>h@#UjVT%{Q39iA58Ths0J3s|NohoLF&Lt!8(8c18V>XAjZFc|1vT%{s#lF z^Kb%2CZ>-cJ^%y|<6Q;@;r#qR4;}z*|Nr|B$h_ab1b6QI%fu2>dIV_O?>~RRu82tM4=f|&Ke}8;oX5+eBw}y?G6)5=s|Nnnr@aNATAPEpaEN{Mj=m^OD z&%p5S|G&Tg{{H#<7bL;LEGj9<&cFmz{_j5mJbLs9Ab?m{m{|TZ{D1lB9Z2clKfnI{ z`ThHs^2c9)q;CF`l>Eoa3N#g>nv07INCE^93jWH3lKe}28-VENu!{qxMpm&svFOiawo%#4hT5U&FS z5Yzu(KY%Xz`RDh~-yl6N|NT~b@qz9A2lm1$Hf43G`D-AmnZXVNY5)izCWas1fbRMA z8?52ykLBVV-&q+tkKFpbbOC=r`2SzOfQo=l0_p*hfB*gk2q2IKpzD7Eo%H+Hk6%A8 za57|Q3rv;f;d1r)FDv_xg9F*eKs^8f#KQ37)2~0jMR){${rwHH2k3S7pO3`Z{#jf7 z{|`16Y&=9YkOT-IMh0&|hF9+yelRe6V}O#tcxPkSx96}BCoe=1&?OKCkOT-Y05IoG U$(*n^qyPW_07*qoM6N<$f?|9Y@c;k- literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€ec.png b/java/com.sap.sailing.gwt.ui/images/flags€ec.png new file mode 100644 index 0000000000000000000000000000000000000000..0caa0b1e785295d003869330fc4e073dce07e7f6 GIT binary patch literal 500 zcmV1sCzZm}g`N!}J$oTi?|G$4gL7*^D3`7C}Kmf5Y{CmeN)&f@km*M|^ zrvE_l-~a!AA&BAspa1{=fXIJ9!9O2vbOQts3j+fX{b%^|8m0my0Yd-4N`WN9@BjaR z=no^SIM8~400P%s(0a|A0FHhM?*o#se9Q zEZ;b|7ytr@MWm#zEz$bb`!9d~{{Q>$@1MW_!07MqKOpw+zkh)g(B$8L|49h*Ov!x= z5I`*NZ%IAX?Fbu$UUAQCA3`wxix_2=I& zAouq_SzðCcuS!~%52Kai0?gF&VORsRAR2~rJG2PFT1^!)w@)C_d-AAkU2Vc75Z z*R<@y`9z1vevIh)-p7{p`5C+7f|6l;f1_&?! X)GmJPc-xs)00000NkvXXu0mjfGFPrC literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€eg.png b/java/com.sap.sailing.gwt.ui/images/flags€eg.png new file mode 100644 index 0000000000000000000000000000000000000000..8a3f7a10b5757b006948ea4436fb242d02dc9a4e GIT binary patch literal 465 zcmV;?0WSWDP)LAHVtk{r`=k{y)(2e*gi*sIRYISXlV_^=qKp{(!;n-+xj9 zUjemETFMXP0$m6sfwJP_;%#kh009JYeOg-Dy?gh5gTXH_fG|KLm<2Qhs6|CZ<>JMQ z009IFR-loRl9E6vpeV=!FaTTi8)D~Q7yv2;2q2OXK!5=N{?|@pNV(X=00000NkvXX Hu0mjfG@sA` literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€eh.png b/java/com.sap.sailing.gwt.ui/images/flags€eh.png new file mode 100644 index 0000000000000000000000000000000000000000..90a1195b47a6f12c70d06cb0bd0e4ea88d7bfb03 GIT binary patch literal 508 zcmV`hKmn*~pz%QT=MPXRKmaj;?1ifa0xhloNlE|L zuK&--1mwx?-uoV+`qwW8u#*4+i1F_GyFwg7fByafIr{%Uh)$p>Ae%w(|Nq~=m~|`# z{`_H7QhIal96$iEC^sml1*F~kc<(nb4FCQ91q_kDz!3TkBLDsd`Sky<|4qNomi`s~ z`xoeVfB<3v8uI)9A4wibp!A=AfB%5B0nwj-e?TNKsQ>@@`|oeijK3Q@{{o!^5I~F! y*$fQd7#Mzm(H|HCnf(WhfND7yc3x%x2rvM-AWsdQI)rrq0000&KpNnxA)<^73~VwoKqoOWF#%15i9uxn0tlqx)vH&?arx`ryF**H{9<5mxO9m@ mNC@PFfB*h~9RdUZ0R{j9;Y1$IN+(bN0000&(jx%j7OGE_~DVuFcQkgj@33fJv($pjj zgoNxWFM>pG#K4X+%S_Ys!f>f$mib36%ekHNec$;y5njB{!+Y`YzVGwA&4mTVh%ikU z03gD2Hn&LPeNu%hDG7PUvzrphs|^(-as$IIo1LmPya%Hc0Qn*6qc4XX3oKoa+Z)_XBQk8 znPA)XelBh#6J<)fj|w>7X+~Yun^@Bp4$+N z6L8rb{%QnJN{fql*fJH1L*2YjUlB~CXS&&LY)1V3h&68|x1_5-(4l3HUgs~3JvLXI z$_D=zL{dTnq9RK`-w~w|sCYqqA;@OoAE0!{9Gi+cF%zA>5*8OAiXWs z!A~!@Tb_6WJ;mn(q~>CYJ~Oq(|Mc`miY)G1d$)?S_lf*=dz3nd-8+hwz5w#U=!7L- z+Ve0W8Werm#o=KvYxRVVNtM9!poHk%m;Y{gxKdXC|Y{ fc0^aUlspXz7vm>S7OoCUZUIwXLGJ6E%Z+~lY(hhH literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€es.png b/java/com.sap.sailing.gwt.ui/images/flags€es.png new file mode 100644 index 0000000000000000000000000000000000000000..c2de2d7111e3cb59cf6511dd2ab045e824bdb43e GIT binary patch literal 469 zcmV;`0V@89P)@|4`Xj5kLT%`al?B=W5I`&pe;NKW0^Ri&h`xRJ_x;0v zUa?=y?0^3M|NZ~}FE9c#{{3cP{Qd6}13&;Vf!z&M{pZWqKYu4MFm$tgedF}w=P#IQ z7-9gT-$11R0mKA$(qEu4%ok$*y!^wMRm*x;`R7|k6yu?K{s8?55I{^|9{?Tjhec2I zv+6&FhFWG_BbNVc|Ns94tNRJp!0`V!Py;{!F#$2enB#XZaohg-5%Tlk#oa&nzQW9g zl0Y{D4gK?n0U&@Fe=;yIr=|V7caH%YEYL84k`Tt9-wc2LGODP&y?7BIfLMT@X8Qey zK~fSFpuiXa$^kk7RCwBA zWQbH``0|MX0{;DB`1Ob3-!Fz=zZw28fY1*HhF@R=VQ?@21P}|ur+3w` z_wT=dfByab`{x&s{PXYspTB>919^Y{{Qd`I{N9v10U&@_7=ExZ{APUe{`KE~Al1MB z{rb!Jhml3<_uqeCzux)%bv7|NkFAvw@aK@N+YWG5`bsF#yj00sZ{|0ReUZ0OJ4u`~d&_lgxzd z_7grGtKje;)ax=32IqJ>VE_O6|Nr{|0Uz@6!2*a0?AgCSJ_s@V{`!jztXlEUzkg2h zOW%AK0ILQg2A~)NKmdU>0L=y=29PKt(~s@sMWK)O87MU|4G0ImPt*+y7s`{{I1L_{;G9FVHyv z0R+;(^!pEkq$JpwzYKqVGyVRp{reTr#sB4{{{Q&{G@Ah!GGGK$3=lw!Ux87Egcwk{ eXi`7`5MTfy3O%OUuKb?>0000W@Y84;!#FdBh{DWNV{85G^eR=U#)*62qRvoaLX5p!7Lm^{g>IM$Q zMs{Q_?l?+2NRuC{19iOmU$>t9;*>tnC_qaIU+T2fR7nFyd0Z0-b-MgLN zL~_)yI%8}kwU1=sY!YIJllUc_pyhtI4TcZ)*Lyi~^>58dcXu!D%H5T!AO`R!zyPR# VdiZ30KxO~{002ovPDHLkV1g_p%GUq@ literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€fam.png b/java/com.sap.sailing.gwt.ui/images/flags€fam.png new file mode 100644 index 0000000000000000000000000000000000000000..cf50c759eb28b5962720aa1ce0617a29003e477d GIT binary patch literal 532 zcmV+v0_**WP)SU^I57#IKohy|#uQJDd#=|98& zUrgMLRb~JG{$u$2m+|*M=0AU#{`@s%|H#1h=ih%I`g`z86F>lpk-UOTf}FCMD80)oDIYC+&q4vMR0s0&DpH|L=c>-#`S^3sJ`m zRPz_88i@Y=|MmYr*an6_AO0`^1P}|uzkh%JGXi;k8UFtPIt#4m|G)nbm0000FP2AE)Ir2{}>qlLBSs|`Qg(SfB<4)VqlOE;Q=cD|Nnn$ zna02W|Nj1E&`=Tpav_4q;M$#E00G4E4{SI@`q`VGKvVzz{r4XmU}R+c_2(Zz0I~c7 zs`v*r?Dty(;z&PFFXX zRA5t=4x{1SIibD)Vqy6A2V^D4P_SySA|L?j2ip1XFA)9%V~_%1r~w2J3=}{2Oiu1 f7(RXZ0uW#T>&I!FfdIJb00000NkvXXu0mjfj-u42 literal 0 HcmV?d00001 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€fj.png b/java/com.sap.sailing.gwt.ui/images/flags€fj.png new file mode 100644 index 0000000000000000000000000000000000000000..cee998892eb316c3293ef2d52afec9218bdbbc03 GIT binary patch literal 610 zcmV-o0-gPdP)2C05Lr?a%1G+Sb3M_ z-f!*)-&mJ@lxC7weD@!u;s2li|9<}wjr{Zf&o8mqKR`Cn4*&rGF#yj01QaSLwCD}R z0w(ww8v*|PzTN}jB`Pj8{QK|!{{8;|gOCLd|L9jy6{oELG6Dcq@B)aDq496GGsCmb z5T7wXTnzN$?|=Wl{r{i6vr6{G)xV#={AXc)t!L+QBoiQjSb+Zc`=1dU2n>I~p8E|B z6OfY_{`1$ji1Pn`9_T5yZrhJfj0}g~00a;V(9A!7nZZWFG{8az7^c++|9dI@cmDl* z!Nvb)UorrL86bdIfbsI1fk}{;;V;BV|AE?oY(}v2K-{x07*6Kx`SfB<4-V2A|A=r2_C101z~ iU~vp#0R6xN5MThlzdwv9U#bcK0000pGlSxEDR9HvV*jq}(Fcb#hvlUlhlXhka zzWbmEqA1>PqX;5{xCS4zNl<6JZpH=ao5qte8PZO*7j!r$&;@+_NzVoB6i)y8&BDx; z1!?~H(-z`^g=A=93J&`}5Qn(4(0#EW%qdeyfROqa+~Jo|di_Hqb5kN_d{&IE!7#3Al2be`}!>8(-(eMo?a(v2Sxv>_hxh;<@e{}n+O5+EYI zT8W?u@rVaZLao$7(1VN+2_odpa%}`Hh;IP1B$gT>=s*yWWL@kR{$dv!BWOVUbsBy` zq!wBsNI>FSm&7~FrgN&5HW|5#c7{r(N){s9YCi84$~U|{$Glm>AD0*K|^yLU}ZO+eP) ze;~;4`#0mCzs$dX{r~-&?f370zkUTM{{OB1_1E8DKmYz*JbN)f0I>i8Q1#1Kum1f0 z{pZggpt}FRe*ORX>;JEx|9}4c_w(m__W!pfKK=atLxM{ZXbC_7vHbh@@9*EgK-ECy zzkh)Y{?EYhFEQc&-#`EN9{7Lq7*OE*@9)2U|NQmmC(vO40VtezBLF}U2%?OI(TG)& zJC>!~;SYmN-)zb6gc(7A-^Q(#z0vHb9!CJ#T#ii{@&pjek8j`pfX)5|H00+GpnLvt za{MtfWBC4qk%R63j~{=2{QLgr`wyTb*amx2hce{;{QL3a|BhWh#6A5bKmf5of*%NgdVm^$Zuo!gI_LQd|G=96g51yWl0Agf71rLE? q3XWqq@&Lw?Iyk0*94-cc00RK~MQKxxUU$F%0000 - -Dmongo.host=localhost + -Dmongo.host=127.0.0.1 -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y @@ -184,7 +184,7 @@ no-debug.without-proxy - ${parameters.common} ${parameters.vm-settings} + ${parameters.common} ${parameters.vm-settings} ${parameters.mongodb} @@ -192,7 +192,7 @@ debug.with-proxy - ${parameters.common} ${parameters.vm-settings} ${parameters.proxy} ${parameters.debug} + ${parameters.common} ${parameters.vm-settings} ${parameters.proxy} ${parameters.debug} ${parameters.mongodb} @@ -200,7 +200,7 @@ debug.without-proxy - ${parameters.common} ${parameters.vm-settings} ${parameters.debug} + ${parameters.common} ${parameters.vm-settings} ${parameters.debug} ${parameters.mongodb} From f5d87fc908dc59c1419741c7af015615cc848cfe Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 13 Feb 2015 23:21:20 +0100 Subject: [PATCH 115/264] set bundle context in com.sap.sse.security's Activator.start method right at the beginning --- .../images/flags\020ag.png" | Bin 591 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags`ae.png | Bin 408 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ai.png | Bin 643 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€al.png | Bin 600 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€am.png | Bin 497 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€an.png | Bin 488 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ao.png | Bin 428 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ar.png | Bin 506 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€as.png | Bin 647 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€at.png | Bin 403 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€au.png | Bin 673 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€aw.png | Bin 524 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ax.png | Bin 663 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€az.png | Bin 589 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ba.png | Bin 593 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bb.png | Bin 585 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bd.png | Bin 504 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€be.png | Bin 449 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bf.png | Bin 497 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bg.png | Bin 462 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bh.png | Bin 457 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bi.png | Bin 675 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bj.png | Bin 486 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bm.png | Bin 611 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bn.png | Bin 639 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bo.png | Bin 500 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€br.png | Bin 593 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bs.png | Bin 526 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bt.png | Bin 631 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bv.png | Bin 512 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bw.png | Bin 443 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€by.png | Bin 514 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€bz.png | Bin 600 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ca.png | Bin 628 -> 0 bytes .../images/flags€catalonia.png | Bin 398 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cc.png | Bin 625 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cd.png | Bin 528 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cf.png | Bin 614 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cg.png | Bin 521 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ch.png | Bin 467 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ci.png | Bin 453 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ck.png | Bin 586 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cl.png | Bin 450 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cm.png | Bin 525 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cn.png | Bin 472 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€co.png | Bin 483 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cr.png | Bin 477 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cs.png | Bin 439 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cu.png | Bin 563 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cv.png | Bin 529 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cx.png | Bin 608 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cy.png | Bin 428 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€cz.png | Bin 476 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€de.png | Bin 398 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€dj.png | Bin 572 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€dk.png | Bin 495 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€dm.png | Bin 620 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€do.png | Bin 508 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€dz.png | Bin 582 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ec.png | Bin 500 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€ee.png | Bin 429 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€eg.png | Bin 465 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€eh.png | Bin 508 -> 0 bytes .../images/flags€england.png | Bin 496 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€er.png | Bin 653 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€es.png | Bin 469 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€et.png | Bin 592 -> 0 bytes .../images/flags€europeanunion.png | Bin 479 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€fam.png | Bin 532 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€fi.png | Bin 489 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€fj.png | Bin 610 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags€fk.png | Bin 648 -> 0 bytes .../images/flags€flagsRacecommittee/alpha.png | Bin 497 -> 0 bytes java/com.sap.sailing.gwt.ui/images/flags af.png | Bin 604 -> 0 bytes .../src/com/sap/sse/security/impl/Activator.java | 1 + java/pom.xml | 2 +- 76 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 "java/com.sap.sailing.gwt.ui/images/flags\020ag.png" delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags`ae.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ai.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€al.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€am.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€an.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ao.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ar.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€as.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€at.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€au.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€aw.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ax.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€az.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ba.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bb.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bd.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€be.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bf.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bg.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bh.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bi.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bj.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bm.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bn.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bo.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€br.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bs.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bt.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bv.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bw.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€by.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€bz.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ca.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€catalonia.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cc.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cd.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cf.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cg.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ch.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ci.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ck.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cl.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cm.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cn.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€co.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cr.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cs.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cu.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cv.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cx.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cy.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€cz.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€de.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€dj.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€dk.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€dm.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€do.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€dz.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ec.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€ee.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€eg.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€eh.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€england.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€er.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€es.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€et.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€europeanunion.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€fam.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€fi.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€fj.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€fk.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags€flagsRacecommittee/alpha.png delete mode 100644 java/com.sap.sailing.gwt.ui/images/flags af.png diff --git "a/java/com.sap.sailing.gwt.ui/images/flags\020ag.png" "b/java/com.sap.sailing.gwt.ui/images/flags\020ag.png" deleted file mode 100644 index 556d5504dc28d89be22ec1883f12e8d8c07d5f41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 591 zcmV-V0= z`}|7!`YQYT5C8xIh~*yx1DgQ1_jkSr_QgN{Gn)!7=zkRT=l(yS{y%^Jd$>%yrP!)vVq~xpZ~xA{{HjZ>hB|)Z~OQoBLBm{yCqA0{k;C`&+mV~|1$jj15^wU zKukakbkF}kzkmk({q_6zmUTZaUWBOraqa5!?(V-pgMR}R{04?0&`AIR#PSE^hrdi> z;*7t4gLVFSJ$J5DTibsorq@%ai5h7B2l^1G@*gM=fUW@uAjYc<48V{DX@ijvE(11_ dg@FMezyQzuBHsGv+i3s*002ovPDHLkV1jTADB=JB diff --git a/java/com.sap.sailing.gwt.ui/images/flags`ae.png b/java/com.sap.sailing.gwt.ui/images/flags`ae.png deleted file mode 100644 index ef3a1ecfccdfe9cf7e9fc086a2c6c010f7977ed8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 408 zcmV;J0cZY+P)@{}>pUYZ(~+fDw@M3(WY1!T<=sp}f-o0K!lJ3<>Jc!&VtMslE3ph2^H3FGe(F z4|^}slF1@l1Nxc}^5hjjU=0la|37)k@b@neJ^1|l@87?_{{z(l6@kcKe}Db@_Y267 z<&g#201!YdV6*>2R0EZ*bYA%%2Vi7me5m>mAb?naPGtnD20MY__n-f`0mzg8{s9CK z*hzmtW)s%H$oS{aAAkS?JLxaT`2W9u;n%DD1Q-B%#%TuGpFSS|0000ocPy#thD++(+#W%SS0Qvp^`1%0+_yGC)0QUL-|M&m^ z`T_tk0M7pe&iDc7fh@K74M*k&0{8f*88BG;;GY6ve?;&4{{Q>_{{8&^`u+g@|Ns5} z|C?Nv0*Gb#mu+n4z6ZW~#qjy_|6ji&ZeIEMk16oyzH)i@Pk$MNxLARR?a#kwpMS72 zF)}cG2M8dRUN*~81wCz%$A2$f`_IL|A|w6j^RHtc7{2`lYG-KkQ{(*knSqh%3kzr7 zs%s1kKL7#%F#yj00-^B^D;pD^^AqC)4C(?1m>@q26ceiU4*2{4`u_d~@vQ*ykp&A1 z3h$o${r~^~`vQo?Pm-bY@cm79zVQG5%l!B6)Riap%L`bGemnT<@2|iAfl64!%YWS~ z`p0YY_dig_cYpw5WYAmG*lPLk!;jy8|NZ#~MF0Q%{rCG1ko^Di`(Fhkd3F{?py0c9Vg{{gA~^#>F<|Nj5KapBR^@Bf~CXSi|t;lF=C z8-4)<5DUX^V9+rDCI3RuUy#}V{s46X!}R~}@4ug&fA|+@;n!b(1zCZ900KKMX({{sIILBZE*UQbc|SngJt$+`m9Kz!<;5 dBm+Qz0RWL%NC2%sdjkLf002ovPDHLkV1lARHI@JX diff --git a/java/com.sap.sailing.gwt.ui/images/flags€al.png b/java/com.sap.sailing.gwt.ui/images/flags€al.png deleted file mode 100644 index 92354cb6e257be2cade71cb825027ce8d9efc06d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 600 zcmV-e0;m0nP)PbXFRCwBA zyv)hK@{fUmi2(>${xJOc&A`II@PmN?BKC`c;SU)710$d~Kmf4-r6niy16BY3`}g0! ze=L9h&Uybegp-Ns@87v^zGQMU|NZla@z1Y+zkdVKmFbHB0*HkH4F0@)&iwE1;&GRLuzkX(OGXMMa``@o$|Ni_2x(y(Jn3#TnHT?el|IhC;U%pHK`}6Df z@2@|9J^lXU=Z~KvfBzi*^!4A*U;lv`e*Fge3Lt=(82$q_`~m9z|ND;$!(WEqze~6o ze}X|JFO$H(U+VvX#{c{avi3L7@c;n?3L&6l7=Hix`}emN<6i~#|7+iUWBL8-&##}m zKKx5zXJr5LA80nvkwCM6egFs{po9Y?& zL6B;olm7ns#=rm&Kp-3b{RIXY(1{??Ha=$NKfkS+|9@us$Mfg+93j@fzkY#y2y!;i z&kO(o1PWH5>zPHwn7|>;0Mz;WPl13iP!Y(WKYy4&4hBXMSPugOKmaiw2gk!dq!|4J mj$lZE0R~eis zC&uN{cDQ8`!@ZNLOqwtoG6nhzPx}SV-d6yzFfcIz75{(rngJyDkKrFw8iaoThHCr$ zo8k9wMhS^O3=9AP!~%5B|9}4(ASQtkLN$m1CV&5705X362095KfLKJhW;OjWdGY7l zzrX*$DuLu*pcFC#NdEr)SL!$SY=)lz0mSm_*B_u)|AM4JP)(}33zy1Lc&_o7? zKL7y)bkd){fBym<_v`mxpbeRs`+WRVot-5A{r$IX$CJFg13u{3eX_{0R++j zR}GCkSy{&4zk!krKoTg(2n-k?g31B}5JW!NMq0d0000q< zM7LZb@rX?|r)2RP-+aO3(7q?c3+$-Vv0E)PxM3SDV%@s`#GkZvw_x^WBa~uq3^l}t zWdf(j(=(>^SgETc#5#EZT(4ObRkfxbzP9G;yza0;Ygc8-_*?EP(ca#`l6-Z6D0{tL zQ4|~MCSi!9Q9YkW=V$ix#EqZ!rc?eWA0TwdVID+3aqrYUXUhCI)Ad5!(cP!BhhW$Ayb2&r8kK!bz*2`~UE W(Ndrek0Jg50000NL7TW{{R0! zJP>i}(MpzaSkj z=eqvqa`^|6{{QbQRQ11q3~Vg_kG}Z}5I`(hw}ncX8D4$|h64jUK>or*=^r$dfI%n0 z$3MKz;!F0w91GjkkV%_X`@4 wKR^hm`X5LwD9*v?4;TUC6Bz$&3;+QJ0QLcJ=WT{TU;qFB07*qoM6N<$f>3bX%K!iX diff --git a/java/com.sap.sailing.gwt.ui/images/flags€as.png b/java/com.sap.sailing.gwt.ui/images/flags€as.png deleted file mode 100644 index 32f30e4ce4eedd22d4f09c4f3a46c52dd064f113..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 647 zcmV;20(kw2P);Rmc0RI30 z|Nj5|{Q>>`0R8>{{QLs@`vM01^D6+%Ap+Y10Qw3Sr3ED)s=c}b05Jg0{{#dC0RR60 z)z$y}`~dv>0Q&p?0Q>ne0Khr|#}EMN1`-ttJvaFq6VTDg`OMJS*4F}vg#m0fQ1$QM z42u6QTCg3Kt8+Li1J#Y_zUd>4Lw{URe1tEjLbKmY(S0M7pd(g2qsJ0|z;{R`~d z`n<6E*3bb41p*Zr{{R344LkeqyaMyV01O!W0PZ0+TjlEQ0st`p&i?@b0Q{Gi0m{n% z`T6-B``1JQx+4kM0{rp_FfRy7P6Qq%_1|&<`|JPx`Uew8u)+%h2+ zdiAI7xt}_}e|$CmbI9$2>u h*D)|iF)#oG7yty8`q2#I8zBGy002ovPDHLkV1j5VEF}N{ diff --git a/java/com.sap.sailing.gwt.ui/images/flags€at.png b/java/com.sap.sailing.gwt.ui/images/flags€at.png deleted file mode 100644 index 0f15f34f2883c4b4360fc871d7105309f1533282..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 403 zcmV;E0c`$>P)@|4`Xj5kLT%`al?B=W5I`&prl$WjwHQRjfmQ&G0jUOA z@&|Dug_Rm`2Y251|$~)1M2@@6mI}!8O6olw6y^Q z5X--d7nzS8`+x5q12kBmVFD!~j6c5_fMKno0(1^Q0I>i=is|LjXX40RRttS6cG0UZ?;7002ovPDHLkV1fxUnjZiF diff --git a/java/com.sap.sailing.gwt.ui/images/flags€au.png b/java/com.sap.sailing.gwt.ui/images/flags€au.png deleted file mode 100644 index a01389a745d51e16b01a9dc0a707572564a17625..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 673 zcmV;S0$%-zP)>fJ3En$GhGS>sbE%%m3$AD)q?8M9y>88-}kR7#RKlk!P~Y_PLuF7~U~3`~nC7 zF#yj00ZUDdpLsm{7ajP|&HwoK0Usg|6%f4L_{`Mi{rvv-`ukf=Ed&Gs-sA7L!Q7*a zj{*QO0M7pb%?Sw^g@yy{>ihEY{`vU@3=8@G0rvO$i3mOL`~mv-`W+b$Mmr&io5dg< z5v!7q0*L95jt`TzK8Kd(Utv)OSp_aLv){6ccV+Z`{Q2+asKUU&aO3`Kpz6wW8wp`< z28M3{0mSqnB#A*-c*8%1=RD#sSOwMznKA3=e&iEzwo{cA=PgXK`2OQ}gqId83!|%* zA_Kz@fB*n70M7pdECCwp4H&@R`1|(w-}M5x*74i)0}%fAt;XafA{48))#>Z>?CD#}*e}Ret0tl$#*RMZ7}Jl7Z|M45`5*URzH9L z{rmSnPy;{!u>dsyO%meg+00000NkvXX Hu0mjfN{&}S diff --git a/java/com.sap.sailing.gwt.ui/images/flags€aw.png b/java/com.sap.sailing.gwt.ui/images/flags€aw.png deleted file mode 100644 index a3579c2d621069c8128d7cf16440d5e45a3ab3cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 524 zcmV+n0`vWeP)^}0h@W9fA9JOn#opKkr#VO<{LPm{QLg}EdH3Vb1Zzpp zqiZ+XNBm%5{`ViKi{bCTe}Dh~2a+HyfB%6Q|9}7eKVjxEfB<6Q<6r}-{{7;G=hB{; zLB+p+|Ni^;|DXT={s75;AoBO$f4@PDf8sps4FCQC1Q64wUw^h*hy4HZXO3U)@85qw zD*pci8U|DWQu_BVi2MUWKoOv;00M{w=;|*lY_o%kegGx^{`&*A{SSok_usF-P)oof z|A86+0*K|;FQAM6fB*FtoEI&bZG5mq328#as{}<$F zWDQ{Ffz1MH001phxCBNdC_2H6U$AI~GQbEJqg*Ti0R{kQ(yH?TRrCY^ O0000^@RCwBA zRL|V|7QjV)*ul;Rlceqc6W0{`~_8AeMg& z|LUXE8U8c=`}dziv6P|mEu%JjM}pqh@65md|Ni~w->=_)fBgZXe?Nc!ot?WGAOHX{ z0M7pe`uG9>{{ZXo`u+d?{`u+u=j#9V2j%Sc^797z|N8p+{rLO+`TP9&`u!FW2mk>3 z0#G;xDFDDQ2vg|)-@5Zw?JVTrAxNs~5&|AZ;5uWfQ`vUsMGm`>aGWoI7=P|oW>%~E z|Kk8dq@aG@HzdsB>fj@shYzX-a%=q<(frSYmfLIv5{HbzQ${~M_P_y6C&m>3uU0*DFd0+8#0#Gk)^|NLQBRAyCBeB9H+Wn{=Mr@*PJ_ZKV< zB>w@GFfafF5Ks>TBP0ZV{bBt5_ve*sKkwd`OiO#eX6=u=cfX!E#rX3NBT(umNC7Yu z0Ro8S7X#4ce?Xr63$phg)Bk64X8r&5``_O`AFf^jc>p5y?+^38KVKLa00M}`wU!~x z=I_1Fe}Df4D*yZI@1I|PfBgnRptt`0`2{iqMpRNPo|?TW>MPOFW(r1_!xlT-yep5|G*@e%^SFxf#DC32@n7=0M7pe00011 zNe=7o{FIuhzQ(sjF~t7>`T+s_0sj00{`>*}`~Uy^|Ni?^Z7Tcx|FW0O0tlpm;s5{T z+df{s^E1rDZN;v4VP>0|8GkY`{`&t1s2YfV{r~st=KUYCl59Xr00M{!Xv@F<41fPH zoWA^R$>xvtF5yd$xc~X{7o-}f=ig7DY9RXc``_>1K-c{N2q2&ahQEIq{`~z1RCDs; zw*~7zIJ!sAKj8J}&!7K)enC|K{|nUc|Mwq|27mwp*#K1f8;Jh=2byv8+}GKw-@AGz z%-rMu^XK3HKtBLA{Qvdq*I%FsKn(x^1k&&qY(LP_Uw?q)|3jz0PF?=a(?4bU?qHzN z5I+E=z&88?2q2IKpz*){`~&I%+VJc5-=Dw#?LYB#!lHLRL7BZ8-NZ5x*TXaKmf6TZ20qEQj`^F;~x-W z05U+*fBxNj@_W(7kJ(ujGnWgh$g%(Z2hso#K#U9wI~hQ+`3Dk_7z`j1tL#*2FTcgW f@C+EF009O7>dRn2w6d?H00000NkvXXu0mjfueTzu diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bb.png b/java/com.sap.sailing.gwt.ui/images/flags€bb.png deleted file mode 100644 index 0df19c71d20d7fdc06e1cba01028983439b2bdae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 585 zcmV-P0=E5$P) zj51&U|NhG$_=oZTA7 z0mJ{l|Ns2{|Nr-Y#^3*c{=WU_!>_-;fvROCxPdkR1P~M0Xa=C_KR`47{sXG+pY!Yf zqu=)*{%)W0>;M0szkdJy_507SKYxDz`3KYh5I`&-cY{^``2$3Z|NsB`^ZWO@y}$qd z{r%_nFQBC$4Is{6hz5WF0@(mI8^j0N`~Tl>LzQ1Ye}a&q>MtPo*RQ`|OMu4x1!@2Y zAfSdne}TsT{`c$8-(P?J0c~)w`~?J`zx?#Dg*yp^z&88=2q2IKpt(TRKniTbum8-y z1bBWi0e#F0wgIH{FVJ|Pxj-8L0tgsNfByUdJMs7b-@h3^8h-!E3i%CU{QCW$1*GB6 ze~`2PfHeF82q2&aU>N+96yg32bmdP5238q{|Gydk0=52OVEN6!@<&OA7Z~7wL16>b z@DCt>7#SFn85mf=5eY;LZ#OUe_l@D-2Zn#Y82<4v{9|E|jkRQ8_`$&N0jL_N03g5s XX0T^_9W~6o00000NkvXXu0mjfXpthO diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bd.png b/java/com.sap.sailing.gwt.ui/images/flags€bd.png deleted file mode 100644 index 076a8bf87c0cedcce47099c6b74b59f2c9d1dbce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 504 zcmVCcfPV}OzZe+)fYC1)`hmpw%>WR9L@~Dk00x0r!@5ksR;+hV0>ZLfIcBS@orh;x zv}95za5W)x_7^}bV3YqpdH?h;NZnuSC%?V#{+0gl`~RQckJx{&l>NO~;@7XgzkdJz zEh{VwR0LSuiyWGR{aKQ z00-R5+27mwp+3@G@Um)kt zzdstU{yWl9SIB_0ft3II3)BD*KrA2|{``^Tm;4PDy`=a1tTre>fEI#8 u{{2z<2NVVaum*qtVq}m+iAaI~Aiw~?reC_kVQV=60000gsB+3J~Dn`pdxgmx19A{hgns>oXkhsL8>j&wfIu2Rsv+R_ zAB5T1GyoL?1Q1BWpFe+p|Nf1n;TJ-~Kd@?uhChFRHUI<=#0HS!U%!8$YJib28yLY( z0tg_G2B7i3e*FSj@aq@44gY_`jRywQ69xu=0D?FP=vyd&x*M*V6|CV8P{Uu4OBfgc z0tjNmA4y3`us8lPG6H?}8%zRafgb(Cp!5f*6oP{xAq} rF#LPR@IoIX4%E%Yz;K6w0U*EtuUcjA`-_J300000NkvXXu0mjf+>pV; diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bf.png b/java/com.sap.sailing.gwt.ui/images/flags€bf.png deleted file mode 100644 index ab5ce8fe1237a18d6809a5570024eb108cb14a3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 497 zcmV@|4`Xj5kLT%`al?B=W5I`(ov;U*021-Lr1*!W5(gW7O z@cTDV13&<=05Ky_HBiN$KMcQt819e>gHP`g_|Np;&&;0(C^$RHS>olZ*Q41X9v27}ar6#W9}0h{^{OajIK0?h(y z_zOe~zZd`lhy{p$|NA4!EeST}?;nUxkP49VfMx;1;V%&U|N9rH;SU2q05LM8GXV8K n(H~UyA0);fa6B_H00bBSQ>$p&WfB*UmB!B(-`wPhZ^#?@C^U5EbbPyl_g=3Hc01Sh${{P9=HB%4| z34*<3-m=C?^;L%miR{xQv2*hU;8+Y&0Dxf-W~KjsusWFAC4@j0#9_j;X5z6SjRhH> zd}sd(7FPhVFdSi!*Zj@;_Sc`kfByUdk|3A-`STmZ_yb~qxIp9o{E-p)bLt=iKmf7) z+|Ix(`{&<1pm88mK&}8g1WG~}e||Ik`OB#Cw~m1UAb?na(Zlrn4}+v6P!1@{@CT$5 z&-#|8y{IY2iKmf5omHz(^qAp)ym^JhN+jk7Vet{H$(Z8=>|AV=V zva&!M00M{!p&AHub^kXuF^G#pRQ&(-8^nMkpk@XJfB<6o_wOIv5C1P;X6Wwu|KUB< zc%X*g2-OTg#J~U$KumBafq;(A|A`a+OH08N{X($;=pP0KfB<5Fs|GR7oM!0i{{QYB zSm|$=!=N@Wf}I2qKuka<{f7YtBjf)QCj1u{`~UM7nrdKh0c`*VF+c!8{Q&j%sZ;-Z zd;dRshENS5{{S`og~Sg)05QIL^-54s5b7kL(8GuS8yfz9{>%Ui`+q-vfW*P*^_43C z0mK4S`s2qBh&}(NP5W0_$$%QQ@R)`GfB*vkck5Jby^kNv00000NkvXXu0mjfy0pt~ diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bi.png b/java/com.sap.sailing.gwt.ui/images/flags€bi.png deleted file mode 100644 index 5cc2e30cfc47452d5bef949628e955a522d59e50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 675 zcmV;U0$lxxP)$wbBBJ zMTk#;k4<3e#ePFE;{?TciOx=-NpIdg`t|#dcz^$YFqm+of7AU<7UGr+0t^5F#PsJM z(2)${Jd)SmT+KY5@!|h>po)L5-!r~>3uJux|0`&Jz~QGy6a*CiGyVtq1|Wc#zWw|0 z_y6C2|NnAvaR2)A>-NX%|3Ci{R?!hvH~jzU``ypCK7Rkq&&~h)FDNvCApj5nF#yj0 z0_X_*{{Q{_|NQXv@Av-qBMmR$teo!O*6-%u-J+8r3n~2s`s?uP{Qmp>{`~+900M{! zNC9;}eE(3IS9-C{62AZc<>Jx>%af}0@?r&05Jg0 z{{#T;0Os`NWh`ko4>SM%|Kw(60099Kad7|t0qcf^`tR-^sIP`Fhru7lkI#w#>Hq?W zkzqQ+@08y_|9v}t;{CEE%)%nTc=-MC@P)7S@!~g$((Ud|+28Q224FCZIGXF0FQ1zcbfB*dX_lJRr z;s3wCNa|2D02KoSAbE^!0Dxf-mZ69ApkCb5fP^9ydGO$cm6w`kGj(t#`M{tFlLo%j z*4%mm2&CaJSn02SzkrtfWBLcO;r%bLy5Gnoqrh)qC;VKmZT`F#yj00W%wG zw4zx17##T6*Z=hN0Tvey7!|wG?K;V)0{r{_`}_X<{Qmp<|NQ*_`}@YSvI2-@&cAgG z*R?p0?_>V(@&EVlEP?{x|1esA`||VNU68t8zyJRJ_3zg&hTp&0WMzR?0R#}s1|E|% zRV}tRkH6o2#3;(nsVMXI-pgBmh17ms`TzTu!pfBl%*_9R;OEbWDJei#0R#}sGpDd$ z96Spi@~bn7G5z{~^_!ZLy2#s;5B~o8&G`G*|6f0Ap0WS=_rFd2C(ubi4FCZEF#yj0 z1a!1=2e0}MwgDi3SU@2S4mePl{012eQ4J-3 z|Mm`933BS6zyJRJ1;ztF0D(08|NVzmQWB*4&)@%$um=Y`lmy1W-@hDx{{qQh3=9AP x#JH7#;TJ3-|G=X54_FW_j)5c_0|P*S0RT0dOSD~(4;cUe002ovPDHLkV1hfJDkcB` diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bn.png b/java/com.sap.sailing.gwt.ui/images/flags€bn.png deleted file mode 100644 index 8fb09849e9b5712e9cdd8a2c25035da201535cf5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 639 zcmV-_0)YLAP)8n_8%|dG+hlCr-g% z|9<~wU<9iE{r}%DMphZ1Jpcj3%*DZBWo-@72DFWlk(-gj@!ngb-$#D&C_i9e{QvLY zw|{?r|NHlgiHY~?^IHG`05Jg0{{(k=dF}7->+9=cXlNG}79t%T{rC3_t(pA#`tSX# z4jKOh|NsB|;rr`({{a5__y7Wk5omBxQSrlv5B>nb??0VZ8qCZrpS+?}gatn>o%-+B zPl=+||6st##PspQ2Y>)#{Cu5(QJ~_^?faD-pMV;E|7MwLubjQ@B-gLso$mVYgECpU zxpfp2fEt*XnBKpC4-fz`0M7pb@zVeZ8g>5r-~k2r`Sbhd;{WmN02I(80QmHEv)Ik{ z>H7Nm`}_MdF)?FhWi>T50*LYBMFw`s0-y$le?J(QfB$Fr_2c8e-`_!j`0PG|ckZ*_ zzkY$-`sc5Tips@{7Xbo@1sGC5IYwrQfB%2{|MTZR!ygX*-%McG4cZmTY ZzyK0(F$K@T-Dv;-002ovPDHLkV1la=J3;^e diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bo.png b/java/com.sap.sailing.gwt.ui/images/flags€bo.png deleted file mode 100644 index ce7ba522aa7e948d581478432643c230eed1a658..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 500 zcmV^3LvnC{|x`%yaFl$ss8ha0V@6XKS=c-5cwO(_{}IL0ki=i zfLOjo{AE-9^-mn=h(G`Tfz|!{{r?|W+uz?{^yeR#!Nm9NlRN`J0I_^YV&Ikj@%Q)F z|Ns9m{0FN3^Z!3k!*8HnFvf4N5xas50L!z?-wXsAPk7$ ze<1l+iQ((fPYeJ7#KQ200VpKFA^{3Nph^G!{9^b86oDW}(Ek1R7ZiL9e}Ret0*H}e qBRB&8AR!3%7c6cef(%d+Aiw~vBYd|xMihYn0000j-HAXl7XJGjM{~r+i{r~sxzrQwG-&h#_Y&p;L z=ieV7_s^f-zyJOD{rC5(+EoAn1k&)I;s3Kw&;LT?{{2}P{Pq8T&j0^^J4?LvUd;UK z&+k8ffB*XXONL(tXahh1fi?X94^$0Q>Z$uRRO)Y4)uapmB!2(-ul(c1=C+V!kAF`) z$PCo;`_FHns{jIs3Fu;wy-Z(c2YwAz{&V4aXk?bejFO*Op&u`@i~PecqBpL@DIoojb8mb+WHGK%fG2>)a3=iVf_~v+`s?*1;!`?Kmai^I3i*ZIYtpN f{g(k500bBS@Ns{bTs^2gCr95H^s6Fo1}O2_S$Nfl}4;<}o-r{^R8Q_xtzX z|Nnu?|Ni^;2gm@D3=F@4EC#P1& zzuV{ie)#0qf2Lnh)j(~3VeG$ufPMl9Aczeh3x56n#l!R`HRQLfpFjVwfkN;%I39k( zVgne^e?jDThF>oL0*Hk{T6(^^8pGqq44*zhqZ1OZzkZ>_F(mFeH~<0+0A4S6=>Lb* QN&o-=07*qoM6N<$f&=*Yr2qf` diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bt.png b/java/com.sap.sailing.gwt.ui/images/flags€bt.png deleted file mode 100644 index 1d512dfff42db1ea3e7c59fa7dd69319e789ee12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 631 zcmV--0*L*IP)?&DC3JV7l?ccjcgB=Qq4l4w|Q;eOZ4z|IjsEI`#PYSloM|AFHF zfy)2>XZQ=$@t;lU|F1tRzpnmg{PUOLH{-8A|F{4CvUU?d0I>k|0TuuM^_1cNUy$nm zzZn?*{^!*ACzSEy$NyhHzB0Wn`TXRU=!%=n%Ci4h82|!^32gR%glZtk^6USnb00o@ z{`LDW>%U)&BK_|-y-75RkKVi&O=|xC<=6keK+72b0*D2q;Xlai|Ns9mF#h@fM(h;aV>^!x1-zNu02XV-n@ zm3imZ@&EI0hChG)|NZlafdL?Zn1I0yH26P<=DUx29QQsc%}izF;QV-S-hYOl|1V4z zX|a-<=Kf#g*Z)7i{{y|szyJ_HEKEEM|C#>$KmJzoZa)M2-~Wm-e7Cl+vi)L|kY`}} z&A$F20RV*|BkB*O Rz6SsR002ovPDHLkV1m;fPLlut diff --git a/java/com.sap.sailing.gwt.ui/images/flags€bv.png b/java/com.sap.sailing.gwt.ui/images/flags€bv.png deleted file mode 100644 index 160b6b5b79db15e623fa55e5774e5d160b933180..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 512 zcmV+b0{{JqP)O=a{vExMP2%`MCSoB^FIcLe_%lf;|~%E5I`(IQNh}3Ao>6Q|DFUXMn*>AqQd`w z|1kXd^B;tM|Njjl{{hM0zwd6?1Q0+hV1xeud-4=Wy?p-%sO`^#2S61Jzk!N?s)6X& zzhA%p|N6}=D+{y%Ab`Lc{sL9~1=0UN4*CD*7s%9KAf+JHKs~=eB-8KTKvw|-5R1&; zzd&a|ob(5%^Z$Q=wHy9p13+aOpFRNu5F>N&`Tk_-7w>=n{RejQzkfh&Kn{rf10?_b z{tFTZibx5v&dxav5I~H7|Ney-|DWN1$%1FyagzUW0464;_wU~W1Q5$TW@eGxtUvee z3vAf*8|igK9~@*rr66bh|NrkNM8z+V zAV?>O@ek;bKfu6d00k6 zPW&$KB`I@TtA?2x@Q~7pdcWi#1>DDZ2>MuG0I@IumHuaV^&7|soAMvT0IK){RtF@1 zgH;3B;_Qq-34j1%{P^)BFE1~|NkA(gBv!!4$aw$$eSiRB1ga=2D|__l(H|iA4T2Ex z>lc`SQ9x2&UjE?0g8%`<0`lvhzd$}*14J4{IhY2@0~G@V5J82%q9;EBlXu0U&@_82I`BfBC|Yn~P8llm?myQ3_K18=`^X_ivyEfB<6p_xd%%#*GZ! z-C&_V5IGPDK|g;X*gypg3;+Sd!XPTj0Q7fK5>&$Y{sHB| zjK9DB00sa3QR4q|tc?L6fSCSm+3>HW`NxbYpP7GsX8sK%KS9yYPfWi)GJ)AFzdp14 z{>t>1fsFwmfLQ*Ci!yxq#8guv=_mQ;-(Rp{AoSPbXFRCwBA z{KboB21 z`ppP*BG3|`!vO*asNvt=e_;3gWsp&PA@E<2jYs6ilRy6-zmSku5?~OOP=5aV@4sI_ zSN#QP00zQ!JD>SpAKYgR_QStF|9~F;2M|Ck zz%U1e+FyoWe;B_0VVclq;}vp_kNy3hKMWrj7*{kqJbMcaQ;_Pve;I)x1Q0+h46j}> zeiN4E1lkA+>|e|*mzJsW^M3)l^W_^hf&c&c`Mv->`48yHFF-%s0|+3-*GLfwiRNz% m3}3(u4lrUsie?}H2rvLBbQv(L;??y40000e`b1WGV} ze#gbd^&bp=ef-GykAYc$@BhDltSqdeSy?RXJpcawy+5fJAb?mHcsPJ&{d@EF@4HtJ z)qno||1xd*zaM|Ot4f#|8A1AQ-~Pubz$7IBv;-i4SQvovfB*ge{fFVtAE4aNE7yPi z`^Eg=5yP(^Ur(Q5`TLjM%#`8#_rHuB|9}1hItd_vSpNO`#qjs<|KGp=|NIHm@MXqK z79K7}ZvOwQ9Df%s{`~lX*xI%KzW-!k{Lc96Cr|@G0I@Lq`0?-epMSr9|NHeD?&umBG}6iH(DSogJte zDERNk_kV0Zng09%Itd_vSb&=T{rt@+B*66dJ0my{fpNkvBm!jpV_;xnVgd@XDk?Dx zh=IJyzyJ_HjJJUrz)|>#fr0nRl^;KTz#|yK`2G9$BS%LzureS32rvK^u`=B;c)+&+ O0000d94tALrX!Wj0S6V-v9sr07*qoM6N<$f?*uARsaA1 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cc.png b/java/com.sap.sailing.gwt.ui/images/flags€cc.png deleted file mode 100644 index aed3d3b4e4467c33717ab3e2f61596e06113f9bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 625 zcmV-%0*?KOP)Vbu`-)NXk-Y`p?2$B#e1 z{(buQ`}e;;zyJNd_UF(2w_nb@VUiUP2igD-Kuin_BL6vl`zgIQV*mR8|8G|2U!T7I zn`-)Ow$bxne}9LGfB5z5*XcJ*K(;aeSD>o^0st`p&i@1e@&c~g4N@7~{}B9V!5sbp z`j|K8<@Nq-&kt=P>@pAZ`T7Q#;RiPl^j;kSgdzX}05Jg0{{#R8=f>y-z~LG0`u_d< z{QLg=2?6_k)DH^)`=sFo8YTUi-v;{r`o8J{6Wj>^)c^vB2^cJY|Nnmc_5b@He}DY@ z_51JdufKoSslWR1>-W9)3`r;XfsXn0`_Fc($H!lT!V@5Xn1Igu{qOIuf50&L{qg7D z?|*(LofMGa_%_k-*{}b_0g?9mMSwvE)Bq4bEIr*1})*zF_+U)&LMdEMOb{Nb-vOh6FrNC(tkd{&4;N%g_D$&)>g> z0^hO}m?z%iIsOJ1mka;_#K>R{i_RZNQ42x8VJSe8f#Ern2@qfa%&RHCyvg>Q00000 LNkvXXu0mjf=TkSf diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cd.png b/java/com.sap.sailing.gwt.ui/images/flags€cd.png deleted file mode 100644 index 5e489424884d2ec9e429f70d69af00edf242a077..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 528 zcmV+r0`L8aP)JkPqeYl28iLgD=0{><0$P44T5yOrT$dE?(KkwMFdoG^-J zGv9P)Kk|i5`lcNgUUAbboca5{hI)v&h!9!~`Yg)Ld}$VwYqqXn@gVLi>3LSVGm1W? z3qnDJAk6chH(u7f~FohUBCxfQDx8?5BQsCcprAnfVhO SHC~zk0000@|NZ~|iEi8i zh~?j3h8^dV0y&$fY|i?}`2XK;hJSzm|G$6#|L@;V)&KqZ3sU;$?>`_ZF38C6g#jRd z7@s_ODJ3Nev=9gw8UKSQhX4P)z5oCE{eQ>y|1j|H-+x9%#`EXT0R#{W1H*5i5@u$g zs{agN2m-%;fixfipeaCQpFe*F2q2cU!)f`}`5%6M06O*WzrTN({{8*;CkCkD_wPBk z=llWket~@P=ieVecEOTuB>(}$vTge|RYlbYUmpDW_v_E!KfnL|{{8n?G|>28zowm@ z_UrGj-+zAtrGTUoztWctUjPD#h2amw@BhDn5OHz(52cFPg zot=T34qlT?57Mzkg5?0ssOG0Ftp>paW8OyZ`_I07*qoM6N<$f+2k} AjsO4v diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cg.png b/java/com.sap.sailing.gwt.ui/images/flags€cg.png deleted file mode 100644 index a859792ef32a02b41503b5ab5f216191af397e02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 521 zcmV+k0`~ohP)i@P`2b{=v9^Ktp0uZ001fLMSM)r!?%mH+B91 z=ii?+(ckX8zrd=&8g~5t-|*}I50LQ;KzRlRfB=Hn08;wv->)Q*Kc0LLgTdsU-~VfV z{r~#=|1XdRMzE6r0tlo5X#B6gzhXrG`18Y*0!g6iieLY~Kvgqz}`W zLWO>Z3Lv@r7+CdZpiy8Ae}EeP0z(_<2Y>)#Vfe-1#`njMUlORAfdMG=o8iiD#%aG9 z#Q!h|feZ$rzkeYJ=p=vuV)VVru zEbxddW?+yz2*QkqGed%bf-LEdzK#qG8~eHcB(ehehXZ^X_BJxLg#P@)J?{042oSeSe+Wur< z_>-3Q%f;oFz5UOhKY#rA@%{VvZb^+n3& ztpbN0X6XF;skA}xPTBI?pa0Yu6bOX)nsXTH-p(}r%l>H!zi-^*0%xF$7(8A5T-G@y GGywpKXU=^9 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€ci.png b/java/com.sap.sailing.gwt.ui/images/flags€ci.png deleted file mode 100644 index 3f2c62eb4d7dc192036af889b593b782dbe8abac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 453 zcmV;$0XqJPP)^Az=(+fAb?mHzA!MjRs*H~|NF=I z{~td;@BjZG-hUVsqZ#w(|L@=b|NQ>{d(*K^00G3pzycQk`jp|{UxvSb;p*Vv_V?Sr z{{Q;@@3$6WKrBG#F*5vR z`2XiW&+Xo6{KsbP_-SF+DJF`pL$*;0gaz z7NKVhyo`U;4*0+SK#>rfFFfafF5DUYXKh;r+@n*scPQUy6`!63S zGXn!71H*qtMn-04W+o=4|487=moEST#KiEAA;m(3;Xgy1iO{3BKc9a12~-UM7^Z`) z1qdLZI~bN-c=z}J|D88K{QmR*&tHcBxB*ZDKmf5Ya0q<-!`is-{h$8~f7m(x{QZSX zH3Jv`1P}|uy*o^wIGI2G`1kie)4zYe_&EQ6|NftW0ohon1}3I|Z{7d|5aVBMp0ssVo0Ek~;W>Z#0SJsA+2j`G% zv|UVeYYs-#Sn6_J90h1VosR?LBU7{U1rQ6+R0f9sPrg3=`~UA>#=n0a|7Q67_y6zT zP{j57|G!`V{{zu)Mn+knB>(|98e;E17s>tTLaMG-~Y>h{r`!g0jL-tfS4HmF#P%V_xIo5zyJLP zk|2W%e*G8w^1X|KC618W{eAoCFX+EI diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cn.png b/java/com.sap.sailing.gwt.ui/images/flags€cn.png deleted file mode 100644 index 89144146219e6fbec7eaa89e1bf4b073d299569e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmV;}0Vn>6P)$bmtfBa_T{rmsVufH!rO2O)W0!b+P{TrwO zAb?mv&i(NBbu;G`sX*)cv$d*%>MiL-(QdhpkjakVmyu%k;sfcNRj;yhJaxT5MTg0u5&QfH8#Bf O0000XbC_7v4G9~kE;3? z$h?1GrT>4y7{3|*{r>Zd0U&@_82$jY{AFPH^Plk#hy=QU5o|Ds0oDLyFn~2M0QE2c z1P~*G;PKK9g@-SH{rUU<_aC4k|G@~v`1Kn|{`&=C|M{yZ!G37cNq_)iVfexD=MNA8 zh5p0Hf4_c1v;p<}|Ak}_F!%rh2&CcXZ-(!`82&@8$XA^gF6?xq}zd#y5N`WeVBbyB+frxZH0- diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cs.png b/java/com.sap.sailing.gwt.ui/images/flags€cs.png deleted file mode 100644 index 8254790ca72f98d9e79d94bdfcb8839b1fd434ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 439 zcmV;o0Z9IdP)|s3jQ%L{DG0k=ogG+0SLgMnA-pV!aytqt1(q3VU5g2cTadE%W~lnu`}m; z-504zOD10s2K8+~RqF^K7O;8$o;>*sR0>r0`!|sM`x~eNg#P~i_4n8BzrTI~MgGdl z@-i@d0|+1%Q&SyHO(9WHu7Cgk{{@--A8b6x2B1zb1BygNIks=N00WJ{7y^0)%zz1kWJN^&bany+5X-;o*O`wU`+x5q12BMq=+`e0f-vAB ze||Ik`OBc90(3J#0I>iAmFf2%21!Yv97q%>_xm>^i2a8FLV}HlAfRG^0AlbRVNjfdgKH->)<41fMG{Q1Z53yA)}NHF>VM86o=*Z=~EMTLRklg;&|8vY-+ zh5rBh|L@=bKM?ZoKOp1JpMQV<{rmmr|L=eQfFkpA_5cJB<7Wm2?&_*f&z|!oCH%Iq zVPN&@1^xa7gnxhi`TP6#zh6KNfByZH;9_972M|CY z4S#m*VEXjw_teQhfB)LctFzlGTKpda#8eRa1F`|=V+j!s1_l{`0AhU0z`&K2_5c3; z?|=UACdB;@3Hy%?Ffai9!1(6P8-M@;JL%7#4}bn}Wu*W1^8Nqk|9@zxfJk8Yg2Rge z5=!5G`~e6c79IwMkAMHSMfCl8%kXF6O^EHl82Alg`~}6s-@pI={`>#;Kf}J}IRF8~ zc$$I1RCFdZQi0L={}0epF!T#d{sWT?V3G+SzyRi{Q|!NXWpDrh002ovPDHLkV1m+z B7Bv6> diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cv.png b/java/com.sap.sailing.gwt.ui/images/flags€cv.png deleted file mode 100644 index a63f7eaf63c028615b2ded5878b5e14a7dbe962f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 529 zcmV+s0`C2ZP)*82p^=00=`tlmIeOf&y&dk@6oT z&CV8YCOfMzZ7b-;WHc4ffO0*K`UGc#9pHKW{n=1`s=Y4`sB0UGuD|F2(+4xFrs{6A+r|Fh*S$P>SRGs(%l z+O-QHfEa)O{tdAK=ovi517(5A-n@AO5I`)f0sl(Vm><0RZzq2;(QtLuuFT6X6;y@p zb-8u)9gSFj-3}5^z@SqUX4qfA01!aT3<}j|#!Osn3@^V(zxysO&hx#~ZN`f)vS*$d zpLlA?$HBnM&cM&k$j1o^^oKjY0t65vgZ)>ehy*ei5K#*ZyWju7F%Ll?01#jRixNU5 T4U6zw00000NkvXXu0mjf>cZT~ diff --git a/java/com.sap.sailing.gwt.ui/images/flags€cx.png b/java/com.sap.sailing.gwt.ui/images/flags€cx.png deleted file mode 100644 index 48e31adbf4cc0074f40e95f87c1f103b91fe270e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 608 zcmV-m0-ybfP)Uz~~Q*L}q{yKmai^Fs!d`Rbj9@@=xl~KZbvQ|NZ^*|L@=bfBpc` zKOp-38^rz{DEepVv)`L`Yyt=%76t}3hHqR?<~aN{I`!9n&u`}MKs~?z|Ni|Cihlk3 z1w?Q7fI4M?HUI<=3&<7!82Y{{Q;@?^mDN?|=V) z{r~^ll=rtu5CcE}F#**BRkyY(adR>Mc_H{~T5^XRV~oVFSgBv#s=t2z{grn0SJIVV z|Ni~D^5u8)5(a<(0%_pmV<{@S{pr)+Nt0E6|KyV7{&xHKulY}Z0S*2Ic2cIyuhSoY z&3*a@=p=vu0%~ALNs$10lZEA9e*W!WzkWV{%9A7hYlh}ewm<)V{rd@2{rBIm&Idr} z{RV0P2mmnv&i?}d08{q%2kh+oUtbt0DGA2L@2{yn^Xv%Q%_4hjZ2Q{KfBuh*0fWjOcoPYi>{Qk`ejtlm`TPy!Q@c0|K1n4Ay00P>u ul!4(JQe+}W>@P40kp+Sq42=5$0t^6?P(4CrvcmZQ0000s1`2Y9I{~ve$KWO@Yukru?KTxgz{s9CK3*7Y} zZC_6Qf4TJkuQ&gHzW)Di+kXz$|9k=eg(Cm|XMz~|=g%L20Ahjo4`KiVr|y5|U;qEV z`p@?NKb!P_9;^R?@o<+hfSm*oKp+h;f&YIQ*bg)L>I;A4WVP82*P-nR{;Wu@%{Vvyu7@~4txIf`R>OD+-01E zIfRil07L2S-Mat*#Q65@TRuKMWMi;}EJy%|ff@h;2;_%%@7_UT@edf{0+7H22rvMY Ws9gjvbyTka0000? z0048MLcfb{@Lpld*gfdL?Z zSQvhRtN^J#x6%GQNHxSfxHgc;AD{-1tAIKH0tl!9q}uD*5!1Kl8Kk6va!f$;fJ%WL z`2Cv^NdEc54D$xi27mx!VfgXqT8ME7!~2)OPy-`SXu#NiAkhzFFflLy1Q-A_8F>@M S6G{sJ0000MM3s^~RU@ue&pds5d;g%WqHa}IsktuK5$l>zi01D0l1`Yj zY=(GjEg1}*aO540m-3+Pqh`GN`rbrZcA_w*&^nS}3_aGO)yg)dy_SyqXLYpS>AUqjV{G|yhq<0;nps*=z7?mmLwpBW1MKDL sY;(=&#;M1& diff --git a/java/com.sap.sailing.gwt.ui/images/flags€dj.png b/java/com.sap.sailing.gwt.ui/images/flags€dj.png deleted file mode 100644 index 582af364f8a9cb680628beae33cc9a2dbe0559f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 572 zcmV-C0>k}@P);we;9uK`S<6~zaM{qBL9B<0yBR7V`E_e2q4D) z|Nnpa!Ep64!{fLA8NdLj;oraifB*mg`;Xx-kn#6Fhzn-q&in!pKrBENJRA&WD*ySo z7*5@0`26EP69WTC_22)0z>5C-g{l_hVFa245I`UeKudt6h7^Mc@BgDW81BCO4-x~L z`sXhc{R3+I%fRsKA3y*x{R6rHsA1>M|6jifb2E4w{z{xB{`~z5CPC;Y7bk|9<`c2PXgjR$^CT@Hzz$KrBEfF#?VC z^aSdB^z+g5SJMxCJOGqNsQwQkfr0#&=?~CJ009Kjz|71H^!MIRd#Ajb^76;aUyQ$y z%m(TN#spBq-#`C>zGeUjAdrR+|30kwu=eoBL!3-pGMq9%bs!`E|ACMovw

    4;Zk2 z8GbPU1Q5%#7t@Nb6*GKbU;u{yA29j{CVzn$|6qa)V3LCYAiw~8(_SNKujRx50000< KMNUMnLSTY(1rd4x diff --git a/java/com.sap.sailing.gwt.ui/images/flags€dk.png b/java/com.sap.sailing.gwt.ui/images/flags€dk.png deleted file mode 100644 index e2993d3c59ae78855f777c158a6aae6c1fb5c843..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 495 zcmVh!ZNvLM`<}kPiIA3?K?Zl!VJuS0ABN12uI2v;s z000mK68GQM4oDR3?|C6;zBc4LR82Q1eETXSa+3nD0Ad8%4|Ml`Fn}2U{~ypshW{9V zk%{T!hYtV&#KHiVV*o?2zW>+&Bgm+K00G4EikX==E9>w5`yf~S`o*<00G4K6dZ++hy)_Bw{QPEdi2K7 l5H1Kw2asrHVqgFWFaQRwS@oh;XP^K8002ovPDHLkV1foV*8Tth diff --git a/java/com.sap.sailing.gwt.ui/images/flags€dm.png b/java/com.sap.sailing.gwt.ui/images/flags€dm.png deleted file mode 100644 index 5fbffcba3cb0f20016c9717614127b89db4c9664..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 620 zcmV-y0+aoTP)yt}{r~U(pZ|aU z{rk@#!dhJXj+vP`D&$&!2yPR?Jud5I`&tqo00z_V3?cpb$_)%#~li z5Bw?p&G@T5&5h~tB<;UIJ-`3_mgbQL+5iwhOdtm^{QZlh+Tg>NcGo{qR#UR9ewx4g zlzHy^uRp(j{rmOj?;oHBfB*n70M7pb{ow!s5r+W$=Kufw0RQ~_o$a1N^GPoTT-yiw z&XGVBSbe$r0&xrf|M~#~9P-zx0st`p&i?@b004Y@cH`sX`~3X;`}>j`1PlZ1WGkKd zF1~FN+w&9T}cJeJULy4V8r?0tNHM6WN&<2126ppbC05A-~q$vMCOd&N)3^rn&3WaiZo>@dB zxpL5=L>h@#UjVT%{Q39iA58Ths0J3s|NohoLF&Lt!8(8c18V>XAjZFc|1vT%{s#lF z^Kb%2CZ>-cJ^%y|<6Q;@;r#qR4;}z*|Nr|B$h_ab1b6QI%fu2>dIV_O?>~RRu82tM4=f|&Ke}8;oX5+eBw}y?G6)5=s|Nnnr@aNATAPEpaEN{Mj=m^OD z&%p5S|G&Tg{{H#<7bL;LEGj9<&cFmz{_j5mJbLs9Ab?m{m{|TZ{D1lB9Z2clKfnI{ z`ThHs^2c9)q;CF`l>Eoa3N#g>nv07INCE^93jWH3lKe}28-VENu!{qxMpm&svFOiawo%#4hT5U&FS z5Yzu(KY%Xz`RDh~-yl6N|NT~b@qz9A2lm1$Hf43G`D-AmnZXVNY5)izCWas1fbRMA z8?52ykLBVV-&q+tkKFpbbOC=r`2SzOfQo=l0_p*hfB*gk2q2IKpzD7Eo%H+Hk6%A8 za57|Q3rv;f;d1r)FDv_xg9F*eKs^8f#KQ37)2~0jMR){${rwHH2k3S7pO3`Z{#jf7 z{|`16Y&=9YkOT-IMh0&|hF9+yelRe6V}O#tcxPkSx96}BCoe=1&?OKCkOT-Y05IoG U$(*n^qyPW_07*qoM6N<$f?|9Y@c;k- diff --git a/java/com.sap.sailing.gwt.ui/images/flags€ec.png b/java/com.sap.sailing.gwt.ui/images/flags€ec.png deleted file mode 100644 index 0caa0b1e785295d003869330fc4e073dce07e7f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 500 zcmV1sCzZm}g`N!}J$oTi?|G$4gL7*^D3`7C}Kmf5Y{CmeN)&f@km*M|^ zrvE_l-~a!AA&BAspa1{=fXIJ9!9O2vbOQts3j+fX{b%^|8m0my0Yd-4N`WN9@BjaR z=no^SIM8~400P%s(0a|A0FHhM?*o#se9Q zEZ;b|7ytr@MWm#zEz$bb`!9d~{{Q>$@1MW_!07MqKOpw+zkh)g(B$8L|49h*Ov!x= z5I`*NZ%IAX?Fbu$UUAQCA3`wxix_2=I& zAouq_SzðCcuS!~%52Kai0?gF&VORsRAR2~rJG2PFT1^!)w@)C_d-AAkU2Vc75Z z*R<@y`9z1vevIh)-p7{p`5C+7f|6l;f1_&?! X)GmJPc-xs)00000NkvXXu0mjfGFPrC diff --git a/java/com.sap.sailing.gwt.ui/images/flags€eg.png b/java/com.sap.sailing.gwt.ui/images/flags€eg.png deleted file mode 100644 index 8a3f7a10b5757b006948ea4436fb242d02dc9a4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 465 zcmV;?0WSWDP)LAHVtk{r`=k{y)(2e*gi*sIRYISXlV_^=qKp{(!;n-+xj9 zUjemETFMXP0$m6sfwJP_;%#kh009JYeOg-Dy?gh5gTXH_fG|KLm<2Qhs6|CZ<>JMQ z009IFR-loRl9E6vpeV=!FaTTi8)D~Q7yv2;2q2OXK!5=N{?|@pNV(X=00000NkvXX Hu0mjfG@sA` diff --git a/java/com.sap.sailing.gwt.ui/images/flags€eh.png b/java/com.sap.sailing.gwt.ui/images/flags€eh.png deleted file mode 100644 index 90a1195b47a6f12c70d06cb0bd0e4ea88d7bfb03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 508 zcmV`hKmn*~pz%QT=MPXRKmaj;?1ifa0xhloNlE|L zuK&--1mwx?-uoV+`qwW8u#*4+i1F_GyFwg7fByafIr{%Uh)$p>Ae%w(|Nq~=m~|`# z{`_H7QhIal96$iEC^sml1*F~kc<(nb4FCQ91q_kDz!3TkBLDsd`Sky<|4qNomi`s~ z`xoeVfB<3v8uI)9A4wibp!A=AfB%5B0nwj-e?TNKsQ>@@`|oeijK3Q@{{o!^5I~F! y*$fQd7#Mzm(H|HCnf(WhfND7yc3x%x2rvM-AWsdQI)rrq0000&KpNnxA)<^73~VwoKqoOWF#%15i9uxn0tlqx)vH&?arx`ryF**H{9<5mxO9m@ mNC@PFfB*h~9RdUZ0R{j9;Y1$IN+(bN0000&(jx%j7OGE_~DVuFcQkgj@33fJv($pjj zgoNxWFM>pG#K4X+%S_Ys!f>f$mib36%ekHNec$;y5njB{!+Y`YzVGwA&4mTVh%ikU z03gD2Hn&LPeNu%hDG7PUvzrphs|^(-as$IIo1LmPya%Hc0Qn*6qc4XX3oKoa+Z)_XBQk8 znPA)XelBh#6J<)fj|w>7X+~Yun^@Bp4$+N z6L8rb{%QnJN{fql*fJH1L*2YjUlB~CXS&&LY)1V3h&68|x1_5-(4l3HUgs~3JvLXI z$_D=zL{dTnq9RK`-w~w|sCYqqA;@OoAE0!{9Gi+cF%zA>5*8OAiXWs z!A~!@Tb_6WJ;mn(q~>CYJ~Oq(|Mc`miY)G1d$)?S_lf*=dz3nd-8+hwz5w#U=!7L- z+Ve0W8Werm#o=KvYxRVVNtM9!poHk%m;Y{gxKdXC|Y{ fc0^aUlspXz7vm>S7OoCUZUIwXLGJ6E%Z+~lY(hhH diff --git a/java/com.sap.sailing.gwt.ui/images/flags€es.png b/java/com.sap.sailing.gwt.ui/images/flags€es.png deleted file mode 100644 index c2de2d7111e3cb59cf6511dd2ab045e824bdb43e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 469 zcmV;`0V@89P)@|4`Xj5kLT%`al?B=W5I`&pe;NKW0^Ri&h`xRJ_x;0v zUa?=y?0^3M|NZ~}FE9c#{{3cP{Qd6}13&;Vf!z&M{pZWqKYu4MFm$tgedF}w=P#IQ z7-9gT-$11R0mKA$(qEu4%ok$*y!^wMRm*x;`R7|k6yu?K{s8?55I{^|9{?Tjhec2I zv+6&FhFWG_BbNVc|Ns94tNRJp!0`V!Py;{!F#$2enB#XZaohg-5%Tlk#oa&nzQW9g zl0Y{D4gK?n0U&@Fe=;yIr=|V7caH%YEYL84k`Tt9-wc2LGODP&y?7BIfLMT@X8Qey zK~fSFpuiXa$^kk7RCwBA zWQbH``0|MX0{;DB`1Ob3-!Fz=zZw28fY1*HhF@R=VQ?@21P}|ur+3w` z_wT=dfByab`{x&s{PXYspTB>919^Y{{Qd`I{N9v10U&@_7=ExZ{APUe{`KE~Al1MB z{rb!Jhml3<_uqeCzux)%bv7|NkFAvw@aK@N+YWG5`bsF#yj00sZ{|0ReUZ0OJ4u`~d&_lgxzd z_7grGtKje;)ax=32IqJ>VE_O6|Nr{|0Uz@6!2*a0?AgCSJ_s@V{`!jztXlEUzkg2h zOW%AK0ILQg2A~)NKmdU>0L=y=29PKt(~s@sMWK)O87MU|4G0ImPt*+y7s`{{I1L_{;G9FVHyv z0R+;(^!pEkq$JpwzYKqVGyVRp{reTr#sB4{{{Q&{G@Ah!GGGK$3=lw!Ux87Egcwk{ eXi`7`5MTfy3O%OUuKb?>0000W@Y84;!#FdBh{DWNV{85G^eR=U#)*62qRvoaLX5p!7Lm^{g>IM$Q zMs{Q_?l?+2NRuC{19iOmU$>t9;*>tnC_qaIU+T2fR7nFyd0Z0-b-MgLN zL~_)yI%8}kwU1=sY!YIJllUc_pyhtI4TcZ)*Lyi~^>58dcXu!D%H5T!AO`R!zyPR# VdiZ30KxO~{002ovPDHLkV1g_p%GUq@ diff --git a/java/com.sap.sailing.gwt.ui/images/flags€fam.png b/java/com.sap.sailing.gwt.ui/images/flags€fam.png deleted file mode 100644 index cf50c759eb28b5962720aa1ce0617a29003e477d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 532 zcmV+v0_**WP)SU^I57#IKohy|#uQJDd#=|98& zUrgMLRb~JG{$u$2m+|*M=0AU#{`@s%|H#1h=ih%I`g`z86F>lpk-UOTf}FCMD80)oDIYC+&q4vMR0s0&DpH|L=c>-#`S^3sJ`m zRPz_88i@Y=|MmYr*an6_AO0`^1P}|uzkh%JGXi;k8UFtPIt#4m|G)nbm0000FP2AE)Ir2{}>qlLBSs|`Qg(SfB<4)VqlOE;Q=cD|Nnn$ zna02W|Nj1E&`=Tpav_4q;M$#E00G4E4{SI@`q`VGKvVzz{r4XmU}R+c_2(Zz0I~c7 zs`v*r?Dty(;z&PFFXX zRA5t=4x{1SIibD)Vqy6A2V^D4P_SySA|L?j2ip1XFA)9%V~_%1r~w2J3=}{2Oiu1 f7(RXZ0uW#T>&I!FfdIJb00000NkvXXu0mjfj-u42 diff --git a/java/com.sap.sailing.gwt.ui/images/flags€fj.png b/java/com.sap.sailing.gwt.ui/images/flags€fj.png deleted file mode 100644 index cee998892eb316c3293ef2d52afec9218bdbbc03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 610 zcmV-o0-gPdP)2C05Lr?a%1G+Sb3M_ z-f!*)-&mJ@lxC7weD@!u;s2li|9<}wjr{Zf&o8mqKR`Cn4*&rGF#yj01QaSLwCD}R z0w(ww8v*|PzTN}jB`Pj8{QK|!{{8;|gOCLd|L9jy6{oELG6Dcq@B)aDq496GGsCmb z5T7wXTnzN$?|=Wl{r{i6vr6{G)xV#={AXc)t!L+QBoiQjSb+Zc`=1dU2n>I~p8E|B z6OfY_{`1$ji1Pn`9_T5yZrhJfj0}g~00a;V(9A!7nZZWFG{8az7^c++|9dI@cmDl* z!Nvb)UorrL86bdIfbsI1fk}{;;V;BV|AE?oY(}v2K-{x07*6Kx`SfB<4-V2A|A=r2_C101z~ iU~vp#0R6xN5MThlzdwv9U#bcK0000pGlSxEDR9HvV*jq}(Fcb#hvlUlhlXhka zzWbmEqA1>PqX;5{xCS4zNl<6JZpH=ao5qte8PZO*7j!r$&;@+_NzVoB6i)y8&BDx; z1!?~H(-z`^g=A=93J&`}5Qn(4(0#EW%qdeyfROqa+~Jo|di_Hqb5kN_d{&IE!7#3Al2be`}!>8(-(eMo?a(v2Sxv>_hxh;<@e{}n+O5+EYI zT8W?u@rVaZLao$7(1VN+2_odpa%}`Hh;IP1B$gT>=s*yWWL@kR{$dv!BWOVUbsBy` zq!wBsNI>FSm&7~FrgN&5HW|5#c7{r(N){s9YCi84$~U|{$Glm>AD0*K|^yLU}ZO+eP) ze;~;4`#0mCzs$dX{r~-&?f370zkUTM{{OB1_1E8DKmYz*JbN)f0I>i8Q1#1Kum1f0 z{pZggpt}FRe*ORX>;JEx|9}4c_w(m__W!pfKK=atLxM{ZXbC_7vHbh@@9*EgK-ECy zzkh)Y{?EYhFEQc&-#`EN9{7Lq7*OE*@9)2U|NQmmC(vO40VtezBLF}U2%?OI(TG)& zJC>!~;SYmN-)zb6gc(7A-^Q(#z0vHb9!CJ#T#ii{@&pjek8j`pfX)5|H00+GpnLvt za{MtfWBC4qk%R63j~{=2{QLgr`wyTb*amx2hce{;{QL3a|BhWh#6A5bKmf5of*%NgdVm^$Zuo!gI_LQd|G=96g51yWl0Agf71rLE? q3XWqq@&Lw?Iyk0*94-cc00RK~MQKxxUU$F%0000 - -ea -Xmx1024m -XX:PermSize=256m -XX:+UseG1GC + -ea -Xmx1024m -XX:+UseG1GC -Dtractrac.tunnel=true -Dtractrac.tunnel.host=localhost From 6b6c6574523e812fc4df0e7ad457e4c2a59c19b3 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Sat, 14 Feb 2015 06:51:39 +0100 Subject: [PATCH 116/264] fix test failures due to previous refactoring of abstract replication test classes --- .../test/AbstractServerReplicationTest.java | 5 +++ .../test/ConnectionResetAndReconnectTest.java | 10 +++--- .../test/PrematureOperationReceiptTest.java | 23 ++++++------ .../test/RaceLogReplicationTest.java | 35 ++++++------------- .../test/RegattaLogReplicationTest.java | 23 +++--------- .../SimpleMailServiceReplicationTest.java | 5 +-- .../AbstractServerReplicationTestSetUp.java | 2 +- 7 files changed, 41 insertions(+), 62 deletions(-) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java index 723dfa453bd..211737f2c3c 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractServerReplicationTest.java @@ -14,6 +14,11 @@ import com.sap.sse.mongodb.MongoDBService; public abstract class AbstractServerReplicationTest extends com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest { protected ServerReplicationTestSetUp testSetUp; + protected AbstractServerReplicationTest(ServerReplicationTestSetUp testSetUp) { + super(testSetUp); + this.testSetUp = testSetUp; + } + public AbstractServerReplicationTest() { super(new ServerReplicationTestSetUp()); testSetUp = (ServerReplicationTestSetUp) super.testSetUp; diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java index 65da3647ea9..170313fa30c 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/ConnectionResetAndReconnectTest.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.UUID; import java.util.logging.Logger; -import org.junit.Before; import org.junit.Test; import com.rabbitmq.client.Channel; @@ -33,6 +32,10 @@ import com.sap.sse.replication.impl.ReplicationMasterDescriptorImpl; public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTest { static final Logger logger = Logger.getLogger(ConnectionResetAndReconnectTest.class.getName()); + public ConnectionResetAndReconnectTest() { + super(new ServerReplicationTestSetUp()); + } + public static boolean forceStopDelivery = false; static class QueuingConsumerTest extends QueueingConsumer { @@ -81,9 +84,8 @@ public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTe } - protected static class ServerReplicationTestSetUp extends + private static class ServerReplicationTestSetUp extends com.sap.sailing.server.replication.test.AbstractServerReplicationTest.ServerReplicationTestSetUp { - @Before @Override public void setUp() throws Exception { try { @@ -106,7 +108,7 @@ public class ConnectionResetAndReconnectTest extends AbstractServerReplicationTe /* until here both instances should have the same in-memory state. * now lets add an event on master and stop the messaging queue. */ stopMessagingExchange(); - testSetUp.getReplicaReplicator().startToReplicateFrom(testSetUp.getMasterDescriptor()); + replicaReplicator.startToReplicateFrom(masterDescriptor); Event event = addEventOnMaster(); Thread.sleep(1000); // wait for master queue to get filled assertNull(replica.getEvent(event.getId())); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java index 325e88864d0..9eddbb77f1f 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java @@ -15,20 +15,21 @@ import com.sap.sailing.server.operationaltransformation.CreateFlexibleLeaderboar import com.sap.sse.common.Util.Pair; import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.impl.ReplicationReceiver; -import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class PrematureOperationReceiptTest extends AbstractServerReplicationTest { - private ReplicationReceiver replicator; + private static ReplicationReceiver replicator; + + public PrematureOperationReceiptTest() { + super(new ServerReplicationTestSetUp()); + } - /** - * Drops the test DB. Sets up master and replica, starts the JMS message broker and registers the replica with the master. - */ - @Before - @Override - public void setUp() throws Exception { - Pair, ReplicationMasterDescriptor> result = - basicSetUp(/* dropDB */ true, /* master=null means create a new one */ null, /* replica=null means create a new one */ null); - replicator = replicaReplicator.startToReplicateFromButDontYetFetchInitialLoad(result.getB(), /* startReplicatorSuspended */ true); + private static class ServerReplicationTestSetUp extends AbstractServerReplicationTest.ServerReplicationTestSetUp { + @Override + public void setUp() throws Exception { + Pair, ReplicationMasterDescriptor> result = + basicSetUp(/* dropDB */ true, /* master=null means create a new one */ null, /* replica=null means create a new one */ null); + replicator = replicaReplicator.startToReplicateFromButDontYetFetchInitialLoad(result.getB(), /* startReplicatorSuspended */ true); + } } /** diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java index 791aaa82e29..44fb244da54 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RaceLogReplicationTest.java @@ -38,36 +38,23 @@ import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.operationaltransformation.AddColumnToLeaderboard; import com.sap.sailing.server.operationaltransformation.AddColumnToSeries; import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard; import com.sap.sailing.server.operationaltransformation.RenameLeaderboard; import com.sap.sse.common.Util; -import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.replication.ReplicationMasterDescriptor; -import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; public class RaceLogReplicationTest extends AbstractLogReplicationTest { - - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; private RaceLogEvent raceLogEvent; private RaceLogEvent anotherRaceLogEvent; private AbstractLogEventAuthor author = new LogEventAuthorImpl("Test Author", 1); @Before - @Override - public void setUp() throws Exception { + public void createEvents() throws Exception { raceLogEvent = RaceLogEventFactory.INSTANCE.createRaceStatusEvent(new MillisecondsTimePoint(1), author, 42, RaceLogRaceStatus.UNKNOWN); anotherRaceLogEvent = RaceLogEventFactory.INSTANCE.createRaceStatusEvent(new MillisecondsTimePoint(2), author, 42, RaceLogRaceStatus.UNKNOWN); - try { - replicationDescriptorPair = basicSetUp(/* dropDB */ true, null, null); - } catch (Exception e) { - e.printStackTrace(); - tearDown(); - } } @Test @@ -78,7 +65,7 @@ public class RaceLogReplicationTest extends AbstractLogReplicationTest { - - private Pair, ReplicationMasterDescriptor> replicationDescriptorPair; private RegattaLogEvent regattaLogEvent; // private RegattaLogEvent anotherRegattaLogEvent; @@ -38,17 +32,10 @@ public class RegattaLogReplicationTest extends } @Before - @Override - public void setUp() throws Exception { + public void createEvent() throws Exception { final Competitor competitor = TrackBasedTest.createCompetitor("Test Competitor"); regattaLogEvent = new RegattaLogRegisterCompetitorEventImpl(t(0), author, t(0), 0, competitor); // anotherRegattaLogEvent = new RegattaLogRegisterCompetitorEventImpl(t(1), author, t(1), 1, null); - try { - replicationDescriptorPair = basicSetUp(/* dropDB */ true, null, null); - } catch (Exception e) { - e.printStackTrace(); - tearDown(); - } } @Test @@ -58,7 +45,7 @@ public class RegattaLogReplicationTest extends final String fleetName = "Default"; Regatta regatta = setupRegatta(RegattaImpl.getDefaultName(regattaName, BOAT_CLASS_NAME_49er), seriesName, fleetName, BOAT_CLASS_NAME_49er); RegattaLog masterLog = regatta.getRegattaLog(); - replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB()); + replicaReplicator.startToReplicateFrom(masterDescriptor); RegattaLog replicaLog = getReplicaLog(regatta); addAndValidateEventIds(masterLog, replicaLog); } @@ -71,7 +58,7 @@ public class RegattaLogReplicationTest extends Regatta regatta = setupRegatta(RegattaImpl.getDefaultName(regattaName, BOAT_CLASS_NAME_49er), seriesName, fleetName, BOAT_CLASS_NAME_49er); RegattaLog masterLog = regatta.getRegattaLog(); masterLog.add(regattaLogEvent); - replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB()); + replicaReplicator.startToReplicateFrom(masterDescriptor); RegattaLog replicaLog = getReplicaLog(regatta); addAndValidateEventIds(masterLog, replicaLog); } @@ -84,7 +71,7 @@ public class RegattaLogReplicationTest extends Regatta masterRegatta = setupRegatta(RegattaImpl.getDefaultName(regattaName, BOAT_CLASS_NAME_49er), seriesName, fleetName, BOAT_CLASS_NAME_49er); RegattaLog masterLog = masterRegatta.getRegattaLog(); masterLog.add(regattaLogEvent); - replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB()); + replicaReplicator.startToReplicateFrom(masterDescriptor); RegattaLog replicaLog = getReplicaLog(masterRegatta); addAndValidateEventIds(masterLog, replicaLog, regattaLogEvent); } @@ -95,7 +82,7 @@ public class RegattaLogReplicationTest extends FlexibleLeaderboard masterLeaderboard = setupFlexibleLeaderboard(leaderboardName); RegattaLog masterLog = masterLeaderboard.getRegattaLog(); masterLog.add(regattaLogEvent); - replicationDescriptorPair.getA().startToReplicateFrom(replicationDescriptorPair.getB()); + replicaReplicator.startToReplicateFrom(masterDescriptor); RegattaLog replicaLog = getReplicaLog(masterLeaderboard); addAndValidateEventIds(masterLog, replicaLog, regattaLogEvent); } diff --git a/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java b/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java index 2cc72bad1b1..7692ee59b1e 100755 --- a/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java +++ b/java/com.sap.sse.mail.replication.test/src/com/sap/sse/mail/replication/test/SimpleMailServiceReplicationTest.java @@ -6,18 +6,15 @@ import static org.hamcrest.Matchers.equalTo; import org.junit.Test; import com.sap.sse.common.mail.MailException; -import com.sap.sse.mail.MailService; import com.sap.sse.mail.replication.testsupport.AbstractMailServiceReplicationTest; public class SimpleMailServiceReplicationTest extends AbstractMailServiceReplicationTest { @Test public void testReplicationOfSendMail() throws InterruptedException, IllegalAccessException, MailException { - MailService master = testSetUp.getMaster(); - MailService replica = testSetUp.getReplica(); master.sendMail("test@ing.me", "oh what a beatiful mail", "to be sent on this day"); - testSetUp.getReplicaReplicator().waitUntilQueueIsEmpty(); + replicaReplicator.waitUntilQueueIsEmpty(); Thread.sleep(3000); assertThat("mail was not sent on replica", diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java index 2145e775726..912a95e821b 100644 --- a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java @@ -51,7 +51,7 @@ public abstract class AbstractServerReplicationTestSetUp replicaReplicator; private ReplicaDescriptor replicaDescriptor; private ReplicationServiceImpl masterReplicator; - protected ReplicationMasterDescriptor masterDescriptor; + protected ReplicationMasterDescriptor masterDescriptor; protected AbstractServerReplicationTestSetUp() { servletPort = DEFAULT_SERVLET_PORT; From aedbfbeac4eff5cbcd6264a1cfa6c7830ce894d8 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Sat, 14 Feb 2015 06:54:15 +0100 Subject: [PATCH 117/264] remove unused import --- .../server/replication/test/PrematureOperationReceiptTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java index 9eddbb77f1f..18d90dc0e82 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/PrematureOperationReceiptTest.java @@ -5,7 +5,6 @@ import static org.junit.Assert.assertNull; import java.io.IOException; -import org.junit.Before; import org.junit.Test; import com.sap.sailing.domain.leaderboard.Leaderboard; From b14da415186c6130bada3ca62dbaa559b2bfe005 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Sat, 14 Feb 2015 06:47:26 +0000 Subject: [PATCH 118/264] Updated api v1 (markdown) --- wiki/tracking-app/api-v1.md | 73 ++++++++++++++----------------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/wiki/tracking-app/api-v1.md b/wiki/tracking-app/api-v1.md index 25a07363869..b0b79110221 100644 --- a/wiki/tracking-app/api-v1.md +++ b/wiki/tracking-app/api-v1.md @@ -272,11 +272,11 @@ $ curl -H "Content-Type:image/jpeg" --data-binary @ \ # Buoy Tender (Tonnenleger) App

    -An app aimed at buoy tenders. In v1, this only allows marks of an existing course to be _pinged_. The steps for doing so are +An app aimed at buoy tenders. In v1, this only allows marks of a race to be _pinged_. The steps for doing so are 1. provide the information for which leaderboard and race to ping marks -2. get the course information for that race (potentially form the RaceLog, if no RaceDefinition is yet present) -3. ping the marks in that course by submitting fixes +2. get the marks for that race (potentially form the RaceLog, if no TrackedRace is yet present) +3. ping the marks by submitting fixes ## Check-In Information
    @@ -294,57 +294,40 @@ http:///buoy-tender/checkin _also see [Tracking App Check-In Information](#tracking-checkin-info) for additional notes that might apply_ -## Course Information -
    +## Get Marks _see [bug 2651](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2651)_ -**Path:** ``leaderboards/{leaderboard-name}/races/{race-name}/course`` +Do not use the course to get the list of marks to ping. Instead, use the ``RaceLogDefineMarkEvent``s in the RaceLog, or ``TrackedRace#getMarks()``. As the JavaDoc of the latter states, not all marks for a race are necessarily present in the course - e.g. if they are backup buoys to be used in the case of a wind shift. + +**Path:** ``leaderboards/{leaderboard-name}/races/{race-name}/marks`` **Verb:** ``GET`` -**Response:** ([example link](http://www.sapsailing.com/sailingserver/api/v1/regattas/505%20Worlds%202014/races/SAP%20505%20Worlds%20R1/course)) +**Response:** ``` -{ - "name": "SAP 505 Worlds R1", - "waypoints": [ - { - "name": "Start", - "passingInstruction": "None", - "controlPoint": { - "@class": "ControlPointWithTwoMarks", - "name": "Start", - "left": { - "@class": "Mark", - "name": "Start (1)", - "id": "Start (1)", - "type": "BUOY" - }, - "right": { - "@class": "Mark", - "name": "Start (2)", - "id": "Start (2)", - "type": "BUOY" - } - } - }, - { - "name": "Windward", - "passingInstruction": "None", - "controlPoint": { - "@class": "Mark", - "name": "Windward", - "id": "22a53380-046e-0132-0da7-60a44ce903c3", - "type": "BUOY" - } - }, - ... - ] -} +[ + { + "@class": "Mark", + "name": "Start (1)", + "id": "Start (1)", + "type": "BUOY" + }, + { + "@class": "Mark", + "name": "Start (2)", + "id": "Start (2)", + "type": "BUOY" + }, + { + "@class": "Mark", + "name": "Windward", + "id": "22a53380-046e-0132-0da7-60a44ce903c3", + "type": "BUOY" + } +] ``` -_hint: we cannot use [GET regatta/{reg}/races/{race}/course](http://www.sapsailing.com/sailingserver/webservices/api/v1/raceCourseGetDoc.html), as we operate in a leaderboard context (not necessarily regatta), and if case no RaceDefinition exists, this endpoint currently returns no information_ - ## Ping Marks _see [bug 2652](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2652)_ From 6bf7a35e55c18d1ee630f9aad17fde23155a71bd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 14 Feb 2015 08:00:41 +0100 Subject: [PATCH 119/264] ignore SecurityResourceTest.createAccessTokenAndAuthenticate for now on master --- .../src/com/sap/sse/security/test/SecurityResourceTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java index 768ed6f072f..3f78a30a532 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -62,6 +62,7 @@ public class SecurityResourceTest { return accessToken; } + @Ignore("Ignore on master until we've found out what the problem with unloadable UsernameAndPasswordRealm is") @Test public void createAccessTokenAndAuthenticate() throws ParseException { String accessToken = createAccessToken(); From 4b8b667f57555a65f1b43573bac61861f592626d Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Sat, 14 Feb 2015 08:28:45 +0100 Subject: [PATCH 120/264] add REST API doc for smartphone tracking related endpoints --- .../api/v1/competitorTeamGetDoc.html | 46 ++++++++++++++ .../api/v1/competitorTeamImagePostDoc.html | 61 +++++++++++++++++++ .../webservices/api/v1/index.html | 20 +++++- .../api/v1/leaderboardCheckinPostDoc.html | 56 +++++++++++++++++ .../api/v1/leaderboardCheckoutPostDoc.html | 54 ++++++++++++++++ .../api/v1/leaderboardCompetitorGetDoc.html | 46 ++++++++++++++ 6 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamGetDoc.html create mode 100644 java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamImagePostDoc.html create mode 100644 java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckinPostDoc.html create mode 100644 java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckoutPostDoc.html create mode 100644 java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCompetitorGetDoc.html diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamGetDoc.html new file mode 100644 index 00000000000..8cb27f6a6b8 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamGetDoc.html @@ -0,0 +1,46 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/competitors/{id}/team

    + +Description: +

    Gets the details of a competitor's team

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:Json
    Mandatory parameters:None
    Optional parameters:None
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/api/v1/competitors/bba11294-9930-11e0-85be-406186cbf87c/team
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamImagePostDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamImagePostDoc.html new file mode 100644 index 00000000000..b94a26965db --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/competitorTeamImagePostDoc.html @@ -0,0 +1,61 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/competitors/{id}/team/image

    + +Description: +

    Sets the team image

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:JSON
    Mandatory headers:Content-Type: (image-png|image-jpg)
     Content-Length
    Mandatory parameters:None
    Optional parameters:None
    Request method:POST (Binary data, not multipart)
    Request example:
    curl -H "Content-Type:image/jpeg" --data-binary @{image-path} \
    +   http://sapsailing.com/sailingserver/api/v1/competitors/{competitor-id}/team/image
    Response example:
    {
    +  "teamImageUri" : "http://training.sapsailing.com/team_images/9871d3a2c554b27151cacf1422eec048.jpeg"
    +}
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html index 766f399ca1a..ec818730916 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html @@ -96,6 +96,18 @@ name to make sure you get to the event you're interested in. /api/v1/leaderboards/{name} Gets the details of a leaderboard + + /api/v1/leaderboards/{name}/device_mappings/start + Tracking App Device Checkin on leaderboard + + + /api/v1/leaderboards/{name}/device_mappings/end + Tracking App Device Checkout on leaderboard + + + /api/v1/leaderboards/{name}/competitors/{id} + Gets the details of a competitor in the context of a leaderboard + /api/v1/gps_fixes Adds GPS-fixes that originated from a device with a unique identifier @@ -105,8 +117,12 @@ name to make sure you get to the event you're interested in. Gets the details of a competitor - /api/v1/leaderboards/{name}/competitors/{id} - Gets the details of a competitor in the context of a leaderboard + /api/v1/competitors/{id}/team + Gets the details of a competitor's team + + + /api/v1/competitors/{id}/team/image + Sets the team image diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckinPostDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckinPostDoc.html new file mode 100644 index 00000000000..f56526a95b3 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckinPostDoc.html @@ -0,0 +1,56 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/leaderboards/{name}/device_mappings/start

    + +Description: +

    Tracking App Device Checkin on leaderboard

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:HTTP 200 (no body)
    Mandatory parameters:None
    Optional parameters:None
    Request method:POST
    Mandatory input:JSON
     
    {
    +    "competitorId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
    +    "deviceType" : "ios",
    +    "deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
    +    "pushDeviceId" : "0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78",
    +    "fromMillis" : 1415624564718
    +}
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckoutPostDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckoutPostDoc.html new file mode 100644 index 00000000000..8be664a81e1 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCheckoutPostDoc.html @@ -0,0 +1,54 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/leaderboards/{name}/device_mappings/end

    + +Description: +

    Tracking App Device Checkout on leaderboard

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:HTTP 200 (no body)
    Mandatory parameters:None
    Optional parameters:None
    Request method:POST
    Mandatory input:JSON
     
    {
    +    "competitorId" : "af855a56-9726-4a9c-a77e-da955bd289bg",
    +    "deviceUuid" : "af855a56-9726-4a9c-a77e-da955bd289bf",
    +    "toMillis" : 1415624564719
    +}
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCompetitorGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCompetitorGetDoc.html new file mode 100644 index 00000000000..3dbd8660aa3 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardCompetitorGetDoc.html @@ -0,0 +1,46 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/leaderboards/{name}/competitors/{competitorId}

    + +Description: +

    Gets the leaderboard-specific version of a competitor

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:Json
    Mandatory parameters:None
    Optional parameters:None
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/api/v1/leaderboards/{name}/competitors/{id}
    +
    +Back to Web Service Overview + + From 6908175ffbd49e720bc9d6a02f1b169c218066ec Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Sat, 14 Feb 2015 12:02:55 +0000 Subject: [PATCH 121/264] Updated api v1 (markdown) --- wiki/tracking-app/api-v1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/tracking-app/api-v1.md b/wiki/tracking-app/api-v1.md index b0b79110221..d5b029b407f 100644 --- a/wiki/tracking-app/api-v1.md +++ b/wiki/tracking-app/api-v1.md @@ -300,7 +300,7 @@ _see [bug 2651](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2651)_ Do not use the course to get the list of marks to ping. Instead, use the ``RaceLogDefineMarkEvent``s in the RaceLog, or ``TrackedRace#getMarks()``. As the JavaDoc of the latter states, not all marks for a race are necessarily present in the course - e.g. if they are backup buoys to be used in the case of a wind shift. -**Path:** ``leaderboards/{leaderboard-name}/races/{race-name}/marks`` +**Path:** ``leaderboards/{leaderboard-name}/raceColumns/{race-column-name}/marks`` **Verb:** ``GET`` From 27763550eac182df5cb6d23d1da6793836f37e25 Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Sat, 14 Feb 2015 12:21:22 +0000 Subject: [PATCH 122/264] Updated api v1 (markdown) --- wiki/tracking-app/api-v1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/tracking-app/api-v1.md b/wiki/tracking-app/api-v1.md index d5b029b407f..78db7439ae0 100644 --- a/wiki/tracking-app/api-v1.md +++ b/wiki/tracking-app/api-v1.md @@ -300,7 +300,7 @@ _see [bug 2651](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2651)_ Do not use the course to get the list of marks to ping. Instead, use the ``RaceLogDefineMarkEvent``s in the RaceLog, or ``TrackedRace#getMarks()``. As the JavaDoc of the latter states, not all marks for a race are necessarily present in the course - e.g. if they are backup buoys to be used in the case of a wind shift. -**Path:** ``leaderboards/{leaderboard-name}/raceColumns/{race-column-name}/marks`` +**Path:** ``leaderboards/{leaderboard-name}/marks?raceColumn={race-column-name}&fleet={fleet-name}`` **Verb:** ``GET`` From eceabfa81ba60b1f9a268f712cf69f6c31c2db2c Mon Sep 17 00:00:00 2001 From: Fredrik Teschke Date: Sat, 14 Feb 2015 13:40:51 +0100 Subject: [PATCH 123/264] removed duplicate constructors (probably from cherry-pick & merge) --- .../testsupport/AbstractServerReplicationTestSetUp.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java index df85fc4ce60..912a95e821b 100644 --- a/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java +++ b/java/com.sap.sse.replication.testsupport/src/com/sap/sse/replication/testsupport/AbstractServerReplicationTestSetUp.java @@ -61,14 +61,6 @@ public abstract class AbstractServerReplicationTestSetUp Date: Sat, 14 Feb 2015 17:21:21 +0100 Subject: [PATCH 124/264] fixed missing @Ignore import --- .../src/com/sap/sse/security/test/SecurityResourceTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java index 3f78a30a532..40b22430de8 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -16,6 +16,7 @@ import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import com.sap.sse.security.SecurityService; From b4d3157bf0a7c1b5365bbe244ae1bdabad4c40aa Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 14 Feb 2015 23:35:18 +0100 Subject: [PATCH 125/264] renamed target definition again to its original name --- ...alysis-p2-remote-aad.target => race-analysis-p2-remote.target} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename java/com.sap.sailing.targetplatform/definitions/{race-analysis-p2-remote-aad.target => race-analysis-p2-remote.target} (100%) diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote-aad.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target similarity index 100% rename from java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote-aad.target rename to java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target From 60be227a067d9484d418a55cf957fc89252bddd4 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 15 Feb 2015 17:12:19 +0100 Subject: [PATCH 126/264] after uploading the target platform, make sailing/ directory itself readable for all --- .../scripts/uploadRepositoryToServer.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh b/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh index ea28968942c..dc961e780ec 100755 --- a/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh +++ b/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh @@ -22,7 +22,7 @@ echo "Uploading local repository to $REPO_HOME/sailing" scp -r ../../com.sap.sailing.targetplatform.base/target/repository $USER@$SERVER:$REPO_HOME/sailing echo "Making readable for everyone..." -ssh $USER@$SERVER "chmod -R a+r $REPO_HOME/sailing/*" +ssh $USER@$SERVER "chmod -R a+r $REPO_HOME/sailing" echo "Remote files now available:" ssh $USER@$SERVER "ls -lah $REPO_HOME/sailing" From 55334a7b5820956c297694a89ecc31f7d7d8834d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 15 Feb 2015 18:05:06 +0100 Subject: [PATCH 127/264] also make everything executable after target platform upload, meaning the web server can "cd" into the plugins and features directory --- .../scripts/uploadRepositoryToServer.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh b/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh index dc961e780ec..0ef8633fcd8 100755 --- a/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh +++ b/java/com.sap.sailing.targetplatform/scripts/uploadRepositoryToServer.sh @@ -23,6 +23,7 @@ scp -r ../../com.sap.sailing.targetplatform.base/target/repository $USER@$SERVER echo "Making readable for everyone..." ssh $USER@$SERVER "chmod -R a+r $REPO_HOME/sailing" +ssh $USER@$SERVER "chmod -R a+x $REPO_HOME/sailing" echo "Remote files now available:" ssh $USER@$SERVER "ls -lah $REPO_HOME/sailing" From 1f5abc3af978a2015583922c67cc95cd58f701a9 Mon Sep 17 00:00:00 2001 From: EC2 Server Date: Sun, 15 Feb 2015 22:47:16 +0000 Subject: [PATCH 128/264] fix test thread's context class loader to enable finding realm class by Shiro --- .../com/sap/sse/security/test/SecurityResourceTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java index 02cf08f4080..c496e4faf21 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -34,6 +34,9 @@ public class SecurityResourceTest { @Before public void setUp() throws UserManagementException, MailException { PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory().getDatabase().dropDatabase(); + ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + try { final UserStoreImpl store = new UserStoreImpl(); Activator.setTestUserStore(store); UsernamePasswordRealm.setTestUserStore(store); @@ -51,6 +54,9 @@ public class SecurityResourceTest { store.addPermissionForUser("admin", "can do"); store.addPermissionForUser("admin", "event:view:*"); store.addPermissionForUser("admin", "event:edit:123"); + } finally { + Thread.currentThread().setContextClassLoader(oldContextClassLoader); + } } private String createAccessToken() throws ParseException { @@ -62,7 +68,7 @@ public class SecurityResourceTest { return accessToken; } - @Ignore("Ignore on master until we've found out what the problem with unloadable UsernameAndPasswordRealm is") + // @Ignore("Ignore on master until we've found out what the problem with unloadable UsernameAndPasswordRealm is") @Test public void createAccessTokenAndAuthenticate() throws ParseException { String accessToken = createAccessToken(); From 608fe30d3fed9120735eee4887aa0418020f562f Mon Sep 17 00:00:00 2001 From: EC2 Server Date: Sun, 15 Feb 2015 22:48:23 +0000 Subject: [PATCH 129/264] removed obsolete import --- .../src/com/sap/sse/security/test/SecurityResourceTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java index c496e4faf21..1f017b671d2 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java @@ -14,7 +14,6 @@ import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import com.sap.sse.common.mail.MailException; @@ -68,7 +67,6 @@ public class SecurityResourceTest { return accessToken; } - // @Ignore("Ignore on master until we've found out what the problem with unloadable UsernameAndPasswordRealm is") @Test public void createAccessTokenAndAuthenticate() throws ParseException { String accessToken = createAccessToken(); From 4e25b8f7dca03bc34d1072b05e59b7ed19354e47 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 16 Feb 2015 00:00:01 +0100 Subject: [PATCH 130/264] moved sailing GWT security stuff from server package to shared package because it's used by client stuff --- .../gwt/ui/adminconsole/SailingAdminConsoleFeatures.java | 2 +- .../gwt/ui/client/media/MediaPlayerManagerComponent.java | 2 +- .../gwt/ui/{server => client/shared}/security/Permission.java | 2 +- .../gwt/ui/{server => client/shared}/security/Roles.java | 2 +- .../shared}/security/SailingPermissionsForRoleProvider.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/{server => client/shared}/security/Permission.java (89%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/{server => client/shared}/security/Roles.java (84%) rename java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/{server => client/shared}/security/SailingPermissionsForRoleProvider.java (89%) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java index bad4ee7ab36..9759381bd21 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java @@ -1,6 +1,6 @@ package com.sap.sailing.gwt.ui.adminconsole; -import com.sap.sailing.gwt.ui.server.security.Permission; +import com.sap.sailing.gwt.ui.client.shared.security.Permission; import com.sap.sse.gwt.adminconsole.AdminConsoleFeatures; public interface SailingAdminConsoleFeatures { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java index 47146f59fdd..0f0d4a286cf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java @@ -37,7 +37,7 @@ import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; -import com.sap.sailing.gwt.ui.server.security.Roles; +import com.sap.sailing.gwt.ui.client.shared.security.Roles; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.ErrorReporter; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permission.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Permission.java similarity index 89% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permission.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Permission.java index 63e0bc681e2..f55f5a272d9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Permission.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Permission.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.server.security; +package com.sap.sailing.gwt.ui.client.shared.security; public enum Permission implements com.sap.sse.security.shared.Permission { MANAGE_EVENTS, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Roles.java similarity index 84% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Roles.java index 11b2ce52aba..ef6ffae1f98 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/Roles.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Roles.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.server.security; +package com.sap.sailing.gwt.ui.client.shared.security; import com.sap.sse.security.shared.Role; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/SailingPermissionsForRoleProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java similarity index 89% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/SailingPermissionsForRoleProvider.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java index 3c343fa2fec..22dd5f0d8ce 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/security/SailingPermissionsForRoleProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.server.security; +package com.sap.sailing.gwt.ui.client.shared.security; import java.util.Collections; From 26b55ffd3217b243a43c14b2de384e15d142b45e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 16 Feb 2015 00:33:32 +0100 Subject: [PATCH 131/264] moved server-side QR code stuff from sse.common to sse because it's not GWT-compileable --- .../impl/RaceLogTrackingAdapterImpl.java | 2 +- .../.settings/com.google.gwt.eclipse.core.prefs | 2 +- .../security/SailingPermissionsForRoleProvider.java | 11 ++++------- java/com.sap.sse.common/META-INF/MANIFEST.MF | 4 +--- .../security/shared}/PermissionsForRoleProvider.java | 5 ++--- .../sap/sse/security/AbstractUserStoreBasedRealm.java | 6 +++--- java/com.sap.sse/META-INF/MANIFEST.MF | 4 +++- .../src/com/sap/sse}/qrcode/QRCodeGenerationUtil.java | 3 ++- 8 files changed, 17 insertions(+), 20 deletions(-) rename java/{com.sap.sse.security/src/com/sap/sse/security => com.sap.sse.security.common/src/com/sap/sse/security/shared}/PermissionsForRoleProvider.java (66%) rename java/{com.sap.sse.common/src/com/sap/sse/common => com.sap.sse/src/com/sap/sse}/qrcode/QRCodeGenerationUtil.java (98%) diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index b828c230945..c321270dc4d 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -78,8 +78,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.mail.MailException; -import com.sap.sse.common.qrcode.QRCodeGenerationUtil; import com.sap.sse.mail.MailService; +import com.sap.sse.qrcode.QRCodeGenerationUtil; import com.sap.sse.util.impl.NonGwtUrlHelper; public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { diff --git a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs index ba74941703a..a44f1dda0ca 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs @@ -1,4 +1,4 @@ eclipse.preferences.version=1 entryPointModules= filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDQgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC5Ib21lPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDQgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC5BdXRvUGxheTwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+ diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java index 22dd5f0d8ce..f5bcb73cd84 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java @@ -2,18 +2,15 @@ package com.sap.sailing.gwt.ui.client.shared.security; import java.util.Collections; -import org.apache.shiro.authz.Permission; -import org.apache.shiro.authz.permission.WildcardPermission; - -import com.sap.sse.security.PermissionsForRoleProvider; import com.sap.sse.security.shared.DefaultRoles; +import com.sap.sse.security.shared.PermissionsForRoleProvider; public class SailingPermissionsForRoleProvider implements PermissionsForRoleProvider { @Override - public Iterable getPermissions(String role) { - final Iterable result; + public Iterable getPermissions(String role) { + final Iterable result; if (DefaultRoles.ADMIN.getRolename().equals(role)) { - return Collections.singletonList(new WildcardPermission("*")); + return Collections.singletonList("*"); } else { result = Collections.emptyList(); } diff --git a/java/com.sap.sse.common/META-INF/MANIFEST.MF b/java/com.sap.sse.common/META-INF/MANIFEST.MF index 894d74820b1..22a6396d68e 100644 --- a/java/com.sap.sse.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.common/META-INF/MANIFEST.MF @@ -9,10 +9,8 @@ Export-Package: com.sap.sse.common, com.sap.sse.common.filter, com.sap.sse.common.impl, com.sap.sse.common.mail, - com.sap.sse.common.qrcode, com.sap.sse.common.scalablevalue, com.sap.sse.common.search, com.sap.sse.common.util -Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0", - org.apache.servicemix.bundles.zxing;bundle-version="3.1.0" +Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0" Bundle-ActivationPolicy: lazy diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/PermissionsForRoleProvider.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionsForRoleProvider.java similarity index 66% rename from java/com.sap.sse.security/src/com/sap/sse/security/PermissionsForRoleProvider.java rename to java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionsForRoleProvider.java index a30534d9147..11cc02e9a1c 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/PermissionsForRoleProvider.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionsForRoleProvider.java @@ -1,6 +1,5 @@ -package com.sap.sse.security; +package com.sap.sse.security.shared; -import org.apache.shiro.authz.Permission; /** * Roles can imply permissions. For example, an application may define that the role "admin" can do everything by @@ -10,5 +9,5 @@ import org.apache.shiro.authz.Permission; * */ public interface PermissionsForRoleProvider { - Iterable getPermissions(String role); + Iterable getPermissions(String role); } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java index bb08ac9eb29..f08dce240e7 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java @@ -11,7 +11,6 @@ import java.util.logging.Logger; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authz.AuthorizationInfo; -import org.apache.shiro.authz.Permission; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; @@ -20,6 +19,7 @@ import org.osgi.util.tracker.ServiceTracker; import com.sap.sse.common.Util; import com.sap.sse.security.impl.Activator; +import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.shared.UserManagementException; public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { @@ -109,8 +109,8 @@ public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { Util.addAll(getUserStore().getRolesFromUser(username), roles); if (permissionsForRoleProvider != null) { for (String role : roles) { - for (Permission permission : permissionsForRoleProvider.getPermissions(role)) { - ai.addObjectPermission(permission); + for (String permission : permissionsForRoleProvider.getPermissions(role)) { + ai.addStringPermission(permission); } } } diff --git a/java/com.sap.sse/META-INF/MANIFEST.MF b/java/com.sap.sse/META-INF/MANIFEST.MF index ecb82aed73d..f241ed100a1 100644 --- a/java/com.sap.sse/META-INF/MANIFEST.MF +++ b/java/com.sap.sse/META-INF/MANIFEST.MF @@ -10,12 +10,14 @@ Export-Package: com.sap.sse, com.sap.sse.i18n, com.sap.sse.i18n.impl, com.sap.sse.osgi, + com.sap.sse.qrcode, com.sap.sse.util, com.sap.sse.util.impl, com.sap.sse.util.kmeans Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sse.common, - com.sap.sse.shared.android + com.sap.sse.shared.android, + org.apache.servicemix.bundles.zxing;bundle-version="3.1.0" Import-Package: javax.servlet;version="3.1.0", javax.servlet.http;version="3.1.0", org.osgi.framework;version="1.8.0", diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java b/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java similarity index 98% rename from java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java rename to java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java index 9c0dce56450..b4353f7139a 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java +++ b/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java @@ -1,4 +1,4 @@ -package com.sap.sse.common.qrcode; +package com.sap.sse.qrcode; import java.awt.Color; import java.awt.Graphics2D; @@ -22,6 +22,7 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; * QRCode generator based on the ZXing library. * * TODO have a look at repackaging QRGen as OSGi bundle. + * * @author Fredrik Teschke * */ From 648d9fd2da4ece74255e10f46b25002acbfcca94 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 16 Feb 2015 12:14:43 +0100 Subject: [PATCH 132/264] moved some code to a RaceStateSerializer --- .../impl/EventRaceStatesJsonGetServlet.java | 55 ++------------- .../gateway/jaxrs/api/EventsResource.java | 4 +- .../jaxrs/api/RaceStateSerializer.java | 68 +++++++++++++++++++ 3 files changed, 75 insertions(+), 52 deletions(-) create mode 100644 java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java index e769a35ed4e..402417da056 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java @@ -17,21 +17,17 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastFlagsFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; -import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; -import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; -import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.ReadonlyGateStartRacingProcedure; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.racelog.RaceStateOfSameDayHelper; import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; +import com.sap.sailing.server.gateway.jaxrs.api.RaceStateSerializer; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { private static final long serialVersionUID = -4820965681871902242L; @@ -79,6 +75,7 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { result.put(FIELD_EVENT_ID, event.getId().toString()); JSONArray raceStatesLogEntriesJson = new JSONArray(); result.put(FIELD_RACE_STATES, raceStatesLogEntriesJson); + RaceStateSerializer raceStateSerializer = new RaceStateSerializer(); for (CourseArea courseArea : event.getVenue().getCourseAreas()) { if(filterByCourseAreaParam == null || courseArea.getName().equals(filterByCourseAreaParam)) { for (Leaderboard leaderboard : getService().getLeaderboards().values()) { @@ -89,7 +86,7 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { for (Fleet fleet : raceColumn.getFleets()) { if(!filterByDayOffset || isRaceStateOfSameDay(raceColumn, fleet, dayToCheck)) { - JSONObject raceStateJson = createRaceStateJsonObject(raceColumn, fleet); + JSONObject raceStateJson = raceStateSerializer.serialize(new Pair(raceColumn, fleet)); raceStateJson.put("courseAreaName", courseArea.getName()); raceStateJson.put("leaderboardName", leaderboardName); raceStateJson.put("leaderboardDisplayName", leaderboardDisplayName); @@ -122,50 +119,6 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { return result; } - private JSONObject createRaceStateJsonObject(RaceColumn raceColumn, Fleet fleet) { - JSONObject result = new JSONObject(); - result.put("raceName", raceColumn.getName()); - result.put("fleetName", fleet.getName()); - RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet); - result.put("trackedRaceName", raceIdentifier != null ? raceIdentifier.getRaceName() : null); - result.put("trackedRaceId", raceIdentifier != null ? raceIdentifier.toString() : null); - RaceLog raceLog = raceColumn.getRaceLog(fleet); - if (raceLog != null && !raceLog.isEmpty()) { - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(raceLog); - RaceLogRaceStatus status = state.getStatus(); - TimePoint startTime = state.getStartTime(); - TimePoint finishedTime = state.getFinishedTime(); - JSONObject raceLogStateJson = new JSONObject(); - result.put("raceState", raceLogStateJson); - raceLogStateJson.put("startTime", startTime != null ? startTime.toString() : null); - raceLogStateJson.put("endTime", finishedTime != null ? finishedTime.toString() : null); - raceLogStateJson.put("lastStatus", status.name()); - ReadonlyGateStartRacingProcedure procedure = state.getTypedReadonlyRacingProcedure(ReadonlyGateStartRacingProcedure.class); - if (procedure != null) { - raceLogStateJson.put("pathfinderId", procedure.getPathfinder()); - raceLogStateJson.put("gateLineOpeningTime", procedure.getGateLaunchStopTime()); - } - AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog); - RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze(); - LastFlagsFinder lastFlagFinder = new LastFlagsFinder(raceLog); - RaceLogFlagEvent lastFlagEvent = LastFlagsFinder.getMostRecent(lastFlagFinder.analyze()); - if (lastFlagEvent != null) { - setLastFlagField(raceLogStateJson, lastFlagEvent.getUpperFlag().name(), lastFlagEvent.getLowerFlag().name(), lastFlagEvent.isDisplayed()); - } else if (status.equals(RaceLogRaceStatus.UNSCHEDULED) && abortingFlagEvent != null) { - setLastFlagField(raceLogStateJson, abortingFlagEvent.getUpperFlag().name(), abortingFlagEvent.getLowerFlag().name(), abortingFlagEvent.isDisplayed()); - } else { - setLastFlagField(raceLogStateJson, null, null, null); - } - } - return result; - } - - private void setLastFlagField(JSONObject raceLogStateJson, String upperFlagName, String lowerFlagName, Boolean isDisplayed) { - raceLogStateJson.put("lastUpperFlag", upperFlagName); - raceLogStateJson.put("lastLowerFlag", lowerFlagName); - raceLogStateJson.put("displayed", isDisplayed); - } - /** * Tries to convert the given identifier to a UUID. When the identifier is not a UUID, null is returned. * @param identifierToConvert the identifier as a String diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index ce7a2f336a9..57fce1587d5 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -6,6 +6,7 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -71,7 +72,8 @@ public class EventsResource extends AbstractSailingServerResource { @GET @Produces("application/json;charset=UTF-8") @Path("{eventId}/racestates") - public Response getRaceStates(@PathParam("eventId") String eventId) { + public Response getRaceStates(@PathParam("eventId") String eventId, @QueryParam("filterByLeaderboard") String filterByLeaderboard, + @QueryParam("filterByCourseArea") String filterByCourseArea, @QueryParam("filterByDayOffset") String filterByDayOffset) { return Response.noContent().build(); } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java new file mode 100644 index 00000000000..803886e34ad --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java @@ -0,0 +1,68 @@ +package com.sap.sailing.server.gateway.jaxrs.api; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastFlagsFinder; +import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; +import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; +import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.ReadonlyGateStartRacingProcedure; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; + +public class RaceStateSerializer implements JsonSerializer> { + @Override + public JSONObject serialize(Pair raceColumnAndFleet) { + RaceColumn raceColumn = raceColumnAndFleet.getA(); + Fleet fleet = raceColumnAndFleet.getB(); + + JSONObject result = new JSONObject(); + result.put("raceName", raceColumn.getName()); + result.put("fleetName", fleet.getName()); + RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet); + result.put("trackedRaceName", raceIdentifier != null ? raceIdentifier.getRaceName() : null); + result.put("trackedRaceId", raceIdentifier != null ? raceIdentifier.toString() : null); + RaceLog raceLog = raceColumn.getRaceLog(fleet); + if (raceLog != null && !raceLog.isEmpty()) { + ReadonlyRaceState state = ReadonlyRaceStateImpl.create(raceLog); + RaceLogRaceStatus status = state.getStatus(); + TimePoint startTime = state.getStartTime(); + TimePoint finishedTime = state.getFinishedTime(); + JSONObject raceLogStateJson = new JSONObject(); + result.put("raceState", raceLogStateJson); + raceLogStateJson.put("startTime", startTime != null ? startTime.toString() : null); + raceLogStateJson.put("endTime", finishedTime != null ? finishedTime.toString() : null); + raceLogStateJson.put("lastStatus", status.name()); + ReadonlyGateStartRacingProcedure procedure = state.getTypedReadonlyRacingProcedure(ReadonlyGateStartRacingProcedure.class); + if (procedure != null) { + raceLogStateJson.put("pathfinderId", procedure.getPathfinder()); + raceLogStateJson.put("gateLineOpeningTime", procedure.getGateLaunchStopTime()); + } + AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog); + RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze(); + LastFlagsFinder lastFlagFinder = new LastFlagsFinder(raceLog); + RaceLogFlagEvent lastFlagEvent = LastFlagsFinder.getMostRecent(lastFlagFinder.analyze()); + if (lastFlagEvent != null) { + setLastFlagField(raceLogStateJson, lastFlagEvent.getUpperFlag().name(), lastFlagEvent.getLowerFlag().name(), lastFlagEvent.isDisplayed()); + } else if (status.equals(RaceLogRaceStatus.UNSCHEDULED) && abortingFlagEvent != null) { + setLastFlagField(raceLogStateJson, abortingFlagEvent.getUpperFlag().name(), abortingFlagEvent.getLowerFlag().name(), abortingFlagEvent.isDisplayed()); + } else { + setLastFlagField(raceLogStateJson, null, null, null); + } + } + return result; + } + + private void setLastFlagField(JSONObject raceLogStateJson, String upperFlagName, String lowerFlagName, Boolean isDisplayed) { + raceLogStateJson.put("lastUpperFlag", upperFlagName); + raceLogStateJson.put("lastLowerFlag", lowerFlagName); + raceLogStateJson.put("displayed", isDisplayed); + } +} From ea1b6e3dcf7186e2e0b7603ea46510aa56ee2959 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 16 Feb 2015 12:24:37 +0100 Subject: [PATCH 133/264] moved some more code to RaceStateSerializer --- .../impl/EventRaceStatesJsonGetServlet.java | 26 +++---------------- .../jaxrs/api/RaceStateSerializer.java | 22 ++++++++++++++++ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java index 402417da056..7eb8508c592 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java @@ -13,20 +13,13 @@ import javax.servlet.http.HttpServletResponse; import org.json.simple.JSONArray; import org.json.simple.JSONObject; -import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.racelog.RaceStateOfSameDayHelper; import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; import com.sap.sailing.server.gateway.jaxrs.api.RaceStateSerializer; -import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { @@ -85,8 +78,9 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { String leaderboardDisplayName = leaderboard.getDisplayName(); for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { for (Fleet fleet : raceColumn.getFleets()) { - if(!filterByDayOffset || isRaceStateOfSameDay(raceColumn, fleet, dayToCheck)) { - JSONObject raceStateJson = raceStateSerializer.serialize(new Pair(raceColumn, fleet)); + Pair raceColumnAndFleet = new Pair(raceColumn, fleet); + if(!filterByDayOffset || raceStateSerializer.isRaceStateOfSameDay(raceColumnAndFleet, dayToCheck)) { + JSONObject raceStateJson = raceStateSerializer.serialize(raceColumnAndFleet); raceStateJson.put("courseAreaName", courseArea.getName()); raceStateJson.put("leaderboardName", leaderboardName); raceStateJson.put("leaderboardDisplayName", leaderboardDisplayName); @@ -105,20 +99,6 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { } } - private boolean isRaceStateOfSameDay(RaceColumn raceColumn, Fleet fleet, Calendar now) { - boolean result = false; - RaceLog raceLog = raceColumn.getRaceLog(fleet); - if (raceLog != null && !raceLog.isEmpty()) { - TimePoint startTime = new StartTimeFinder(raceLog).analyze(); - TimePoint finishedTime = new FinishedTimeFinder(raceLog).analyze(); - RaceLogFlagEvent abortingFlagEvent = new AbortingFlagFinder(raceLog).analyze(); - TimePoint abortingTime = abortingFlagEvent != null ? abortingFlagEvent.getLogicalTimePoint() : null; - - result = RaceStateOfSameDayHelper.isRaceStateOfSameDay(startTime, finishedTime, abortingTime, now); - } - return result; - } - /** * Tries to convert the given identifier to a UUID. When the identifier is not a UUID, null is returned. * @param identifierToConvert the identifier as a String diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java index 803886e34ad..ba4e7484024 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java @@ -1,11 +1,15 @@ package com.sap.sailing.server.gateway.jaxrs.api; +import java.util.Calendar; + import org.json.simple.JSONObject; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastFlagsFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.ReadonlyGateStartRacingProcedure; @@ -13,6 +17,7 @@ import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.racelog.RaceStateOfSameDayHelper; import com.sap.sailing.server.gateway.serialization.JsonSerializer; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; @@ -65,4 +70,21 @@ public class RaceStateSerializer implements JsonSerializer raceColumnAndFleet, Calendar dayToCheck) { + RaceColumn raceColumn = raceColumnAndFleet.getA(); + Fleet fleet = raceColumnAndFleet.getB(); + + boolean result = false; + RaceLog raceLog = raceColumn.getRaceLog(fleet); + if (raceLog != null && !raceLog.isEmpty()) { + TimePoint startTime = new StartTimeFinder(raceLog).analyze(); + TimePoint finishedTime = new FinishedTimeFinder(raceLog).analyze(); + RaceLogFlagEvent abortingFlagEvent = new AbortingFlagFinder(raceLog).analyze(); + TimePoint abortingTime = abortingFlagEvent != null ? abortingFlagEvent.getLogicalTimePoint() : null; + + result = RaceStateOfSameDayHelper.isRaceStateOfSameDay(startTime, finishedTime, abortingTime, dayToCheck); + } + return result; + } } From eaacd3f4c111f7b382ced718667e115ff1f0ed8c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 16 Feb 2015 00:33:32 +0100 Subject: [PATCH 134/264] moved server-side QR code stuff from sse.common to sse because it's not GWT-compileable Conflicts: java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java java/com.sap.sse.security.common/src/com/sap/sse/security/shared/PermissionsForRoleProvider.java java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java --- .../racelogtracking/impl/RaceLogTrackingAdapterImpl.java | 2 +- .../.settings/com.google.gwt.eclipse.core.prefs | 2 +- java/com.sap.sse.common/META-INF/MANIFEST.MF | 4 +--- .../src/com/sap/sse/security/AbstractUserStoreBasedRealm.java | 1 + java/com.sap.sse/META-INF/MANIFEST.MF | 4 +++- .../src/com/sap/sse}/qrcode/QRCodeGenerationUtil.java | 3 ++- 6 files changed, 9 insertions(+), 7 deletions(-) rename java/{com.sap.sse.common/src/com/sap/sse/common => com.sap.sse/src/com/sap/sse}/qrcode/QRCodeGenerationUtil.java (98%) diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index b828c230945..c321270dc4d 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -78,8 +78,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.mail.MailException; -import com.sap.sse.common.qrcode.QRCodeGenerationUtil; import com.sap.sse.mail.MailService; +import com.sap.sse.qrcode.QRCodeGenerationUtil; import com.sap.sse.util.impl.NonGwtUrlHelper; public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { diff --git a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs index ba74941703a..a44f1dda0ca 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/com.google.gwt.eclipse.core.prefs @@ -1,4 +1,4 @@ eclipse.preferences.version=1 entryPointModules= filesCopiedToWebInfLib=gwt-servlet.jar -gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDQgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC5Ib21lPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\= +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDQgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC5BdXRvUGxheTwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+ diff --git a/java/com.sap.sse.common/META-INF/MANIFEST.MF b/java/com.sap.sse.common/META-INF/MANIFEST.MF index 894d74820b1..22a6396d68e 100644 --- a/java/com.sap.sse.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.common/META-INF/MANIFEST.MF @@ -9,10 +9,8 @@ Export-Package: com.sap.sse.common, com.sap.sse.common.filter, com.sap.sse.common.impl, com.sap.sse.common.mail, - com.sap.sse.common.qrcode, com.sap.sse.common.scalablevalue, com.sap.sse.common.search, com.sap.sse.common.util -Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0", - org.apache.servicemix.bundles.zxing;bundle-version="3.1.0" +Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0" Bundle-ActivationPolicy: lazy diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java index 81453ea9614..8a29ad47380 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java @@ -19,6 +19,7 @@ import org.osgi.util.tracker.ServiceTracker; import com.sap.sse.common.Util; import com.sap.sse.security.impl.Activator; +import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.shared.UserManagementException; public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { diff --git a/java/com.sap.sse/META-INF/MANIFEST.MF b/java/com.sap.sse/META-INF/MANIFEST.MF index ecb82aed73d..f241ed100a1 100644 --- a/java/com.sap.sse/META-INF/MANIFEST.MF +++ b/java/com.sap.sse/META-INF/MANIFEST.MF @@ -10,12 +10,14 @@ Export-Package: com.sap.sse, com.sap.sse.i18n, com.sap.sse.i18n.impl, com.sap.sse.osgi, + com.sap.sse.qrcode, com.sap.sse.util, com.sap.sse.util.impl, com.sap.sse.util.kmeans Bundle-ActivationPolicy: lazy Require-Bundle: com.sap.sse.common, - com.sap.sse.shared.android + com.sap.sse.shared.android, + org.apache.servicemix.bundles.zxing;bundle-version="3.1.0" Import-Package: javax.servlet;version="3.1.0", javax.servlet.http;version="3.1.0", org.osgi.framework;version="1.8.0", diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java b/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java similarity index 98% rename from java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java rename to java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java index 9c0dce56450..b4353f7139a 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/qrcode/QRCodeGenerationUtil.java +++ b/java/com.sap.sse/src/com/sap/sse/qrcode/QRCodeGenerationUtil.java @@ -1,4 +1,4 @@ -package com.sap.sse.common.qrcode; +package com.sap.sse.qrcode; import java.awt.Color; import java.awt.Graphics2D; @@ -22,6 +22,7 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; * QRCode generator based on the ZXing library. * * TODO have a look at repackaging QRGen as OSGi bundle. + * * @author Fredrik Teschke * */ From ef698c16a00c19cda6c061c60df6f8c58e92087a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 16 Feb 2015 15:59:55 +0100 Subject: [PATCH 135/264] removed cherry-picked effects of permission management --- .../src/com/sap/sse/security/AbstractUserStoreBasedRealm.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java index 8a29ad47380..81453ea9614 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java @@ -19,7 +19,6 @@ import org.osgi.util.tracker.ServiceTracker; import com.sap.sse.common.Util; import com.sap.sse.security.impl.Activator; -import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.shared.UserManagementException; public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { From f146b815c9f8e63686841a516e96f35ce4b73681 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 16 Feb 2015 13:58:20 +0100 Subject: [PATCH 136/264] added a EventRaceStatesSerializer --- .../impl/EventRaceStatesJsonGetServlet.java | 59 ++----------- .../jaxrs/api/EventRaceStatesSerializer.java | 87 +++++++++++++++++++ .../gateway/jaxrs/api/EventsResource.java | 21 ++++- 3 files changed, 112 insertions(+), 55 deletions(-) create mode 100644 java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventRaceStatesSerializer.java diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java index 7eb8508c592..1897a5ec74a 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java @@ -1,8 +1,6 @@ package com.sap.sailing.server.gateway.impl; import java.io.IOException; -import java.util.Calendar; -import java.util.Date; import java.util.UUID; import java.util.logging.Logger; @@ -10,16 +8,12 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.json.simple.JSONArray; import org.json.simple.JSONObject; -import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; -import com.sap.sailing.server.gateway.jaxrs.api.RaceStateSerializer; +import com.sap.sailing.server.gateway.jaxrs.api.EventRaceStatesSerializer; import com.sap.sse.common.Util.Pair; public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { @@ -28,33 +22,17 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { private final static Logger logger = Logger.getLogger(EventRaceStatesJsonGetServlet.class.getName()); private static final String PARAM_NAME_EVENTID = "eventId"; - // private static final String PARAM_NAME_FILTER_BY_REGATTA = "filterByRegatta"; private static final String PARAM_NAME_FILTER_BY_LEADERBOARD = "filterByLeaderboard"; private static final String PARAM_NAME_FILTER_BY_COURSEAREA = "filterByCourseArea"; private static final String PARAM_NAME_FILTER_BY_DAYOFFSET = "filterByDayOffset"; - public static final String FIELD_EVENT_NAME = "name"; - public static final String FIELD_EVENT_ID = "id"; - public static final String FIELD_RACE_STATES = "raceStates"; - @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String eventIdParam = request.getParameter(PARAM_NAME_EVENTID); String filterByCourseAreaParam = request.getParameter(PARAM_NAME_FILTER_BY_COURSEAREA); String filterByLeaderboardParam = request.getParameter(PARAM_NAME_FILTER_BY_LEADERBOARD); String filterByDayOffsetParam = request.getParameter(PARAM_NAME_FILTER_BY_DAYOFFSET); - boolean filterByDayOffset = false; - Calendar dayToCheck = Calendar.getInstance(); - dayToCheck.setTime(new Date()); - if(filterByDayOffsetParam != null) { - try { - int dayOffset = Integer.parseInt(filterByDayOffsetParam); - filterByDayOffset = true; - dayToCheck.add(Calendar.DAY_OF_YEAR, dayOffset); - } catch (NumberFormatException e) { - // invalid integer - } - } + if (eventIdParam == null) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "You need to specify a event id using the "+ PARAM_NAME_EVENTID + " parameter"); @@ -63,36 +41,9 @@ public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { if (event == null) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event "+eventIdParam+" not found"); } else { - JSONObject result = new JSONObject(); - result.put(FIELD_EVENT_NAME, event.getName()); - result.put(FIELD_EVENT_ID, event.getId().toString()); - JSONArray raceStatesLogEntriesJson = new JSONArray(); - result.put(FIELD_RACE_STATES, raceStatesLogEntriesJson); - RaceStateSerializer raceStateSerializer = new RaceStateSerializer(); - for (CourseArea courseArea : event.getVenue().getCourseAreas()) { - if(filterByCourseAreaParam == null || courseArea.getName().equals(filterByCourseAreaParam)) { - for (Leaderboard leaderboard : getService().getLeaderboards().values()) { - if (filterByLeaderboardParam == null || leaderboard.getName().equals(filterByLeaderboardParam)) { - if (leaderboard.getDefaultCourseArea() != null && leaderboard.getDefaultCourseArea().equals(courseArea)) { - String leaderboardName = leaderboard.getName(); - String leaderboardDisplayName = leaderboard.getDisplayName(); - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { - for (Fleet fleet : raceColumn.getFleets()) { - Pair raceColumnAndFleet = new Pair(raceColumn, fleet); - if(!filterByDayOffset || raceStateSerializer.isRaceStateOfSameDay(raceColumnAndFleet, dayToCheck)) { - JSONObject raceStateJson = raceStateSerializer.serialize(raceColumnAndFleet); - raceStateJson.put("courseAreaName", courseArea.getName()); - raceStateJson.put("leaderboardName", leaderboardName); - raceStateJson.put("leaderboardDisplayName", leaderboardDisplayName); - raceStatesLogEntriesJson.add(raceStateJson); - } - } - } - } - } - } - } - } + EventRaceStatesSerializer eventRaceStatesSerializer = new EventRaceStatesSerializer(filterByCourseAreaParam, + filterByLeaderboardParam, filterByDayOffsetParam); + JSONObject result = eventRaceStatesSerializer.serialize(new Pair>(event, getService().getLeaderboards().values())); setJsonResponseHeader(response); result.writeJSONString(response.getWriter()); } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventRaceStatesSerializer.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventRaceStatesSerializer.java new file mode 100644 index 00000000000..7b3e83dc649 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventRaceStatesSerializer.java @@ -0,0 +1,87 @@ +package com.sap.sailing.server.gateway.jaxrs.api; + +import java.util.Calendar; +import java.util.Date; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; +import com.sap.sse.common.Util.Pair; + +public class EventRaceStatesSerializer implements JsonSerializer>> { + public static final String FIELD_EVENT_NAME = "name"; + public static final String FIELD_EVENT_ID = "id"; + public static final String FIELD_RACE_STATES = "raceStates"; + + private String filterByCourseArea; + private String filterByLeaderboardName; + private Calendar filterDay; + + public EventRaceStatesSerializer() { + this.filterByCourseArea = null; + this.filterByLeaderboardName = null; + this.filterDay = null; + } + + public EventRaceStatesSerializer(String filterByCourseArea, String filterByLeaderboard, String filterByDayOffset) { + this.filterByCourseArea = filterByCourseArea; + this.filterByLeaderboardName = filterByLeaderboard; + + Integer dayOffset = null; + if(filterByDayOffset != null) { + try { + dayOffset = Integer.parseInt(filterByDayOffset); + filterDay = Calendar.getInstance(); + filterDay.setTime(new Date()); + filterDay.add(Calendar.DAY_OF_YEAR, dayOffset); + } catch (NumberFormatException e) { + // invalid integer + filterDay = null; + } + } + } + + @Override + public JSONObject serialize(Pair> eventAndLeaderboards) { + Event event = eventAndLeaderboards.getA(); + Iterable leaderboards = eventAndLeaderboards.getB(); + + JSONObject result = new JSONObject(); + result.put(FIELD_EVENT_NAME, event.getName()); + result.put(FIELD_EVENT_ID, event.getId().toString()); + JSONArray raceStatesLogEntriesJson = new JSONArray(); + result.put(FIELD_RACE_STATES, raceStatesLogEntriesJson); + RaceStateSerializer raceStateSerializer = new RaceStateSerializer(); + for (CourseArea courseArea : event.getVenue().getCourseAreas()) { + if(filterByCourseArea == null || courseArea.getName().equals(filterByCourseArea)) { + for (Leaderboard leaderboard : leaderboards) { + if (filterByLeaderboardName == null || leaderboard.getName().equals(filterByLeaderboardName)) { + if (leaderboard.getDefaultCourseArea() != null && leaderboard.getDefaultCourseArea().equals(courseArea)) { + String leaderboardName = leaderboard.getName(); + String leaderboardDisplayName = leaderboard.getDisplayName(); + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { + for (Fleet fleet : raceColumn.getFleets()) { + Pair raceColumnAndFleet = new Pair(raceColumn, fleet); + if(filterDay == null || raceStateSerializer.isRaceStateOfSameDay(raceColumnAndFleet, filterDay)) { + JSONObject raceStateJson = raceStateSerializer.serialize(raceColumnAndFleet); + raceStateJson.put("courseAreaName", courseArea.getName()); + raceStateJson.put("leaderboardName", leaderboardName); + raceStateJson.put("leaderboardDisplayName", leaderboardDisplayName); + raceStatesLogEntriesJson.add(raceStateJson); + } + } + } + } + } + } + } + } + return result; + } +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index 57fce1587d5..6da6328ffc9 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -16,12 +16,14 @@ import org.json.simple.JSONObject; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; import com.sap.sailing.server.gateway.serialization.JsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.CourseAreaJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; +import com.sap.sse.common.Util.Pair; @Path("/v1/events") public class EventsResource extends AbstractSailingServerResource { @@ -74,7 +76,24 @@ public class EventsResource extends AbstractSailingServerResource { @Path("{eventId}/racestates") public Response getRaceStates(@PathParam("eventId") String eventId, @QueryParam("filterByLeaderboard") String filterByLeaderboard, @QueryParam("filterByCourseArea") String filterByCourseArea, @QueryParam("filterByDayOffset") String filterByDayOffset) { - return Response.noContent().build(); + Response response; + UUID eventUuid; + try { + eventUuid = UUID.fromString(eventId); + } catch (IllegalArgumentException e) { + return getBadEventErrorResponse(eventId); + } + Event event = getService().getEvent(eventUuid); + if (event == null) { + response = getBadEventErrorResponse(eventId); + } else { + EventRaceStatesSerializer eventRaceStatesSerializer = new EventRaceStatesSerializer(filterByCourseArea, + filterByLeaderboard, filterByDayOffset); + JSONObject raceStatesJson = eventRaceStatesSerializer.serialize(new Pair>(event, getService().getLeaderboards().values())); + String json = raceStatesJson.toJSONString(); + response = Response.ok(json, MediaType.APPLICATION_JSON).build(); + } + return response; } } \ No newline at end of file From da608903b82dbb4c1fa92460f88107cebe6f44a1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 16 Feb 2015 16:36:12 +0100 Subject: [PATCH 137/264] fixed launch configs; fixed mail service which needs to implement ClearStateTestSupport --- .../SailingServer (No Proxy).launch | 4 +- ...ailingServer (No Proxy, Cached MTB).launch | 4 +- ...ingServer (No Proxy, Jetty on 8889).launch | 4 +- ...o Proxy, Jetty on 8889, Cached MTB).launch | 4 +- ...ingServer (No Proxy, Jetty on 8890).launch | 4 +- ...ver (No Proxy, Remote Debug SAP VM).launch | 4 +- ...ailingServer (No Proxy, winddbTest).launch | 4 +- .../SailingServer (Proxy).launch | 78 +++++++++---------- ...ailingServer (Proxy, Jetty on 8889).launch | 4 +- ...Server (Proxy, Remote Debug SAP VM).launch | 4 +- ...Proxy, SwissTiming Live Simulation).launch | 4 +- .../SailingServer (Proxy, winddbTest).launch | 4 +- .../sap/sailing/server/impl/Activator.java | 1 - .../src/com/sap/sse/mail/MailService.java | 3 +- .../sap/sse/mail/impl/MailServiceImpl.java | 5 ++ 15 files changed, 68 insertions(+), 63 deletions(-) diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index c5fab174924..8a9f6b5f5a2 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -30,9 +30,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch index 4e6b443e28e..ded8c572ff2 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch @@ -29,9 +29,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 043acbe3b14..4a87c5dcac8 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -23,10 +23,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index bf8e01d7760..3a7fa0de9c5 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -23,10 +23,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index 0aa36441bde..254ba26fee7 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -22,10 +22,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index f3037bf7b65..723697b6c4f 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -22,9 +22,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch index 84bce884b0c..f14b6637666 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -29,9 +29,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 020997acfac..73c1d33df86 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -1,39 +1,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index f6aafcac67e..49611a87c36 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -23,10 +23,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index 78880eb9552..9e99a5d1946 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -22,9 +22,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index 066903924b2..59df666c599 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -22,9 +22,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 3e55ae7cf5f..1851792927f 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -24,10 +24,10 @@ - + - + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java index 18fb666575d..fb46cb94e1d 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java @@ -73,7 +73,6 @@ public class Activator implements BundleActivator { context.registerService(Replicable.class.getName(), racingEventService, replicableServiceProperties); context.registerService(RacingEventService.class.getName(), racingEventService, null); context.registerService(ClearStateTestSupport.class.getName(), racingEventService, null); - Dictionary properties = new Hashtable(); properties.put(TypeBasedServiceFinder.TYPE, GPSFixImpl.class.getName()); registrations.add(context.registerService(GPSFixMongoHandler.class, diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java index 6578e8b05d6..06017a4c737 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/MailService.java @@ -6,6 +6,7 @@ import com.sap.sse.common.mail.MailException; import com.sap.sse.mail.impl.ReplicableMailService; import com.sap.sse.replication.Replicable; import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; +import com.sap.sse.util.ClearStateTestSupport; /** @@ -21,7 +22,7 @@ import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; * @author Fredrik Teschke * */ -public interface MailService extends ReplicableWithObjectInputStream> { +public interface MailService extends ReplicableWithObjectInputStream>, ClearStateTestSupport { void sendMail(String toAddress, String subject, String body) throws MailException; /** diff --git a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java index 13d0032c104..e46bab2d984 100644 --- a/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java +++ b/java/com.sap.sse.mail/src/com/sap/sse/mail/impl/MailServiceImpl.java @@ -138,6 +138,11 @@ public class MailServiceImpl implements ReplicableMailService { apply(s -> s.internalSendMail(toAddress, subject, multipartContent)); } + @Override + public void clearState() throws Exception { + // nothing to clear for test support + } + // ----------------- Replication ------------- @Override public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException { From bd03ab913734c9ed4628e60f37bd830baf2bfa2c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 16 Feb 2015 16:54:53 +0100 Subject: [PATCH 138/264] adjusted shiro.ini after moving SailingPermissionsForRoleProvider to new package --- java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini index b3d07ec9f34..d1980ba70b0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini @@ -12,7 +12,7 @@ credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 -permissionsForRoleProvider = com.sap.sailing.gwt.ui.server.security.SailingPermissionsForRoleProvider +permissionsForRoleProvider = com.sap.sailing.gwt.ui.client.shared.security.SailingPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher upRealm.permissionsForRoleProvider = $permissionsForRoleProvider From 0d713fa02bbdfb1659e958cb40664e9a85384262 Mon Sep 17 00:00:00 2001 From: fmittag Date: Mon, 16 Feb 2015 18:14:23 +0100 Subject: [PATCH 139/264] added documentation of the new API to the webservices doc --- .../api/v1/eventRaceStatesGetDoc.html | 50 +++++++++++++++++++ .../webservices/api/v1/index.html | 12 +++-- 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 java/com.sap.sailing.server.gateway/webservices/api/v1/eventRaceStatesGetDoc.html diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/eventRaceStatesGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/eventRaceStatesGetDoc.html new file mode 100644 index 00000000000..c1ceefc855f --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/eventRaceStatesGetDoc.html @@ -0,0 +1,50 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 Beta + + +

    SAP Sailing Analytics Webservices API Version 1.0 Beta

    +

    URL: /api/v1/events/{eventId}/racestates

    + +Description: +

    The service gets the race states of all races of an sailing event

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Webservice Type:REST
    Output format:Json
    Mandatory parameters:None
    Optional parameters: +
    filterByLeaderboard (filters the race states by the name of the leaderboard)
    +
    filterByCourseArea (filters the race states by the name of the course area)
    +
    filterByDayOffset (filters the race states by a day offset for current day, e.g. 1 means yesterday)
    +
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/api/v1/events/41de25fd-0f22-4200-bdac-5574e7d5225d/racestates
    +
    +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html index 766f399ca1a..08c69864290 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html @@ -40,6 +40,10 @@ name to make sure you get to the event you're interested in. /api/v1/events/{id} Gets the details of an event + + /api/v1/events/{id}/racestates + Gets the race states of all races of an event + /api/v1/regattas Gets all regattas @@ -96,6 +100,10 @@ name to make sure you get to the event you're interested in. /api/v1/leaderboards/{name} Gets the details of a leaderboard + + /api/v1/leaderboards/{name}/competitors/{id} + Gets the details of a competitor in the context of a leaderboard + /api/v1/gps_fixes Adds GPS-fixes that originated from a device with a unique identifier @@ -104,10 +112,6 @@ name to make sure you get to the event you're interested in. /api/v1/competitors/{id} Gets the details of a competitor - - /api/v1/leaderboards/{name}/competitors/{id} - Gets the details of a competitor in the context of a leaderboard -
    From 73f7ce197fe508fb7fdca120ba309c42a8754ec8 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 16 Feb 2015 23:40:05 +0100 Subject: [PATCH 140/264] whitespace changes only --- .../shared/security/SailingPermissionsForRoleProvider.java | 1 - .../sap/sse/security/ui/server/UserManagementServiceImpl.java | 3 ++- .../src/com/sap/sse/security/UsernamePasswordRealm.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java index f5bcb73cd84..a2978c0a121 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java @@ -16,5 +16,4 @@ public class SailingPermissionsForRoleProvider implements PermissionsForRoleProv } return result; } - } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index 1b80fad731c..9505f3b9f98 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -108,7 +108,8 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U public SuccessInfo login(String username, String password) { try { String redirectURL = getSecurityService().login(username, password); - return new SuccessInfo(true, "Success. Redirecting to "+redirectURL, redirectURL, createUserDTOFromUser(getSecurityService().getUserByName(username))); + return new SuccessInfo(true, "Success. Redirecting to "+redirectURL, redirectURL, + createUserDTOFromUser(getSecurityService().getUserByName(username))); } catch (UserManagementException | AuthenticationException e) { return new SuccessInfo(false, SuccessInfo.FAILED_TO_LOGIN, /* redirectURL */ null, null); } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java index 722cdd52e16..b473a70c388 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java @@ -41,7 +41,7 @@ public class UsernamePasswordRealm extends AbstractUserStoreBasedRealm { String saltedPassword = null; byte[] salt = null; User user = getUserStore().getUserByName(username); - if (user == null){ + if (user == null) { return null; } UsernamePasswordAccount upa = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD); From 1576b00aef10c6b9c9a1adce5f54a1b18fa4e703 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Tue, 17 Feb 2015 13:03:19 +0100 Subject: [PATCH 141/264] Cleaned up least squares implementations and tests --- .../common/PolarSheetGenerationSettings.java | 2 + .../PolarSheetGenerationSettingsImpl.java | 13 +- .../test/PolarSheetGenerationServiceTest.java | 2 +- ...heetGenerationSettingsDialogComponent.java | 2 +- .../mining/test/PolarDataMinerTest.java | 66 ++++++-- .../AnyOrderLeastSquaresRegressionTest.java | 52 ------ .../test/IncrementalLeastSquaresTest.java | 74 +++++++++ ...rementalLinearRegressionOrderOneTest.java} | 120 +++++++------- ...nlineMultiVariateLinearRegressionTest.java | 43 ----- .../polars/test/PolarSheetGenerationTest.java | 4 +- .../META-INF/MANIFEST.MF | 3 +- .../polars/mining/CubicSpeedRegressions.java | 24 +-- .../sailing/polars/mining/PolarDataMiner.java | 8 +- .../regression/IncrementalLeastSquares.java | 31 ++++ .../IncrementalLinearRegressionProcessor.java | 17 -- .../OnlineMultiVariateRegression.java | 26 --- .../impl/AnyOrderLeastSquaresRegression.java | 152 ------------------ .../IncrementalAnyOrderLeastSquaresImpl.java | 133 +++++++++++++++ .../IncrementalLeastSquaresProcessor.java | 130 --------------- .../OnlineMultiVariateRegressionImpl.java | 90 ----------- .../race-analysis-p2-remote.target | 2 + 21 files changed, 387 insertions(+), 607 deletions(-) delete mode 100644 java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java create mode 100644 java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java rename java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/{IncrementalLinearRegressionTest.java => IncrementalLinearRegressionOrderOneTest.java} (55%) delete mode 100644 java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java delete mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java delete mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java delete mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java delete mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java delete mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java index 9b4805cb0a5..e5a2c4f6de0 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PolarSheetGenerationSettings.java @@ -32,4 +32,6 @@ public interface PolarSheetGenerationSettings extends Serializable { boolean areDefault(); + int getNumberOfLeadingCompetitorsToInclude(); + } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java index d1975070b25..8f1ee656f11 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/PolarSheetGenerationSettingsImpl.java @@ -11,7 +11,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. }; WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.5); return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping, - false); + false, 0); } public static PolarSheetGenerationSettings createBackendPolarSettings() { @@ -26,7 +26,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet } WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 0.5); return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping, - false); + false, 1); } private static final long serialVersionUID = 2731616509404813790L; @@ -42,6 +42,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet private boolean useOnlyEstimationForWindDirection; private WindSpeedSteppingWithMaxDistance windStepping; private boolean splitByWindGauges; + private int numberOfLeadingCompetitorsToInclude; //GWT PolarSheetGenerationSettingsImpl() {}; @@ -51,7 +52,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet boolean useOnlyWindGaugesForWindSpeed, boolean shouldRemoveOutliers, double outlierDetectionNeighboorhoodRadius, double outlierMinimumNeighboorhoodPct, boolean useOnlyEstimationForWindDirection, WindSpeedSteppingWithMaxDistance windStepping, - boolean splitByWindGauges) { + boolean splitByWindGauges, int numberOfLeadingCompetitorsToInclude) { this.minimumDataCountPerGraph = minimumDataCountPerGraph; this.minimumWindConfidence = minimumWindConfidence; this.minimumDataCountPerAngle = minimumDataCountPerAngle; @@ -64,6 +65,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet this.useOnlyEstimationForWindDirection = useOnlyEstimationForWindDirection; this.windStepping = windStepping; this.splitByWindGauges = splitByWindGauges; + this.numberOfLeadingCompetitorsToInclude = numberOfLeadingCompetitorsToInclude; } @Override @@ -206,5 +208,10 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet return true; } + @Override + public int getNumberOfLeadingCompetitorsToInclude() { + return numberOfLeadingCompetitorsToInclude; + } + } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java index f0fc52e1ea1..9dd9fc9cd5f 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java @@ -69,7 +69,7 @@ public class PolarSheetGenerationServiceTest { double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. }; WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0); PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5, - 0.05, false, windStepping, false); + 0.05, false, windStepping, false, 0); List idList = new ArrayList(); idList.add(new RegattaNameAndRaceName("IrgendeineRegatta", "IrgendeinRennen")); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetGenerationSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetGenerationSettingsDialogComponent.java index ec09f5a8c49..02a89c6fa47 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetGenerationSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarsheets/PolarSheetGenerationSettingsDialogComponent.java @@ -162,7 +162,7 @@ public class PolarSheetGenerationSettingsDialogComponent implements SettingsDial useOnlyWindGaugesForWindSpeedBox.getValue(), shouldRemoveOutliersBox.getValue(), outlierRadiusBox.getValue(), outlierNeighborhoodPctBox.getValue(), useOnlyEstimationForWindDirectionBox.getValue(), windSteppingBox.getStepping(windSteppingMaxDistanceBox - .getValue()), splitByWindGaugesBox.getValue()); + .getValue()), splitByWindGaugesBox.getValue(), 0 /*TODO*/); } @Override diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java index f42c269a5c4..5c600eab0cb 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java @@ -9,9 +9,12 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -26,12 +29,15 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.PolarSheetGenerationSettings; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl; +import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.MarkPassing; @@ -51,11 +57,26 @@ public class PolarDataMinerTest { private static final int MILLISECONDS_OVER_WHICH_TO_AVERAGE_SPEED = 30; private static final double EPSILON = 1E-4; + + public static PolarSheetGenerationSettings createTestPolarSettings() { + List levelList = new ArrayList(); + for (double levelValue = 0.5; levelValue < 35; levelValue = levelValue + 0.5) { + levelList.add(levelValue); + } + double[] levels = new double[levelList.size()]; + int i=0; + for (Double level : levelList) { + levels[i++] = level; + } + WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 0.5); + return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping, + false, 3); + } @Test public void testGrouping() throws InterruptedException, TimeoutException, NoSuchMethodException, NotEnoughDataHasBeenAddedException { - PolarDataMiner miner = new PolarDataMiner(); + PolarDataMiner miner = new PolarDataMiner(createTestPolarSettings()); BoatClass mockedBoatClass = mock(BoatClass.class); @@ -150,22 +171,26 @@ public class PolarDataMinerTest { track.add(fix); } } - - RaceDefinition mockedRaceDefinition = createMockedRaceDefinition(); - when(mockedRaceDefinition.getBoatClass()).thenReturn(mockedBoatClass); - when(trackedRace.getRace()).thenReturn(mockedRaceDefinition); - - for (Competitor competitor : fixesPerCompetitor.keySet()) { - MarkPassing markpassing = createMockedStartMarkPassing(); - when(trackedRace.getMarkPassing(eq(competitor), any(Waypoint.class))).thenReturn(markpassing); - } - Calendar cal = Calendar.getInstance(); cal.set(2014, 4, 3, 12, 00); TimePoint startOfRace = new MillisecondsTimePoint(cal.getTime()); cal.set(2014, 4, 3, 15, 00); TimePoint endOfRace = new MillisecondsTimePoint(cal.getTime()); + Waypoint finishWaypoint = mock(Waypoint.class); + createWayPoint(fixesPerCompetitor, trackedRace, endOfRace, finishWaypoint); + Waypoint startWaypoint = mock(Waypoint.class); + createWayPoint(fixesPerCompetitor, trackedRace, startOfRace, startWaypoint); + RaceDefinition mockedRaceDefinition = createMockedRaceDefinition(startWaypoint, finishWaypoint); + when(mockedRaceDefinition.getBoatClass()).thenReturn(mockedBoatClass); + when(trackedRace.getRace()).thenReturn(mockedRaceDefinition); + + for (Competitor competitor : fixesPerCompetitor.keySet()) { + MarkPassing markpassing = createMockedStartMarkPassing(); + when(trackedRace.getMarkPassing(eq(competitor), eq(startWaypoint))).thenReturn(markpassing); + } + + when(trackedRace.getStartOfRace()).thenReturn(startOfRace); when(trackedRace.getEndOfRace()).thenReturn(endOfRace); @@ -183,6 +208,21 @@ public class PolarDataMinerTest { return trackedRace; } + private void createWayPoint(Map> fixesPerCompetitor, TrackedRace trackedRace, + TimePoint endOfRace, Waypoint waypoint) { + List markPassings = new ArrayList(); + Iterator competitorIterator = fixesPerCompetitor.keySet().iterator(); + while (competitorIterator.hasNext()) { + MarkPassing markPassing = mock(MarkPassing.class); + when(markPassing.getTimePoint()).thenReturn(endOfRace); + Competitor competitor = competitorIterator.next(); + when(markPassing.getCompetitor()).thenReturn(competitor); + when(trackedRace.getMarkPassing(competitor, waypoint)).thenReturn(markPassing); + markPassings.add(markPassing); + } + when(trackedRace.getMarkPassingsInOrder(waypoint)).thenReturn(markPassings); + } + private MarkPassing createMockedStartMarkPassing() { Calendar cal = Calendar.getInstance(); cal.set(2014, 4, 3, 12, 15); @@ -193,9 +233,11 @@ public class PolarDataMinerTest { return passing; } - private RaceDefinition createMockedRaceDefinition() { + private RaceDefinition createMockedRaceDefinition(Waypoint startWaypoint, Waypoint finishWaypoint) { RaceDefinition raceDefinition = mock(RaceDefinition.class); Course mockedCourse = createMockedCourse(); + when(mockedCourse.getLastWaypoint()).thenReturn(finishWaypoint); + when(mockedCourse.getFirstWaypoint()).thenReturn(startWaypoint); when(raceDefinition.getCourse()).thenReturn(mockedCourse); BoatClass mockedBoatClass = mock(BoatClass.class); when(mockedBoatClass.getManeuverDegreeAngleThreshold()).thenReturn(20.0); diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java deleted file mode 100644 index 0e7a728084b..00000000000 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/AnyOrderLeastSquaresRegressionTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.sap.sailing.polars.regression.test; - -import static org.hamcrest.Matchers.closeTo; -import static org.junit.Assert.assertThat; - -import java.util.Random; - -import org.junit.Test; - -import com.sap.sailing.polars.regression.impl.AnyOrderLeastSquaresRegression; - -public class AnyOrderLeastSquaresRegressionTest { - - - private static final double ERROR = 0.01; - - @Test - public void testNonIncrementalRegression() { - AnyOrderLeastSquaresRegression regression = new AnyOrderLeastSquaresRegression(3); - Random random = new Random(1); - for (int i = 0; i < 100000; i++) { - double x = random.nextDouble() * 40; - regression.addData(function(x) + (random.nextDouble() - 0.5), x); - } - - long startTime = System.nanoTime(); - double[] coeffs = regression.getCoefficiants(); - System.out.println("First calculation " + (System.nanoTime() - startTime) + "ns"); - - startTime = System.nanoTime(); - coeffs = regression.getCoefficiants(); - System.out.println("No calculation " + (System.nanoTime() - startTime) + "ns"); - double x = random.nextDouble() * 40; - regression.addData(function(x) + (random.nextDouble() - 0.5), x); - startTime = System.nanoTime() ; - coeffs = regression.getCoefficiants(); - System.out.println("Second calculation " + (System.nanoTime() - startTime) + "ns"); - - assertThat(coeffs[0], closeTo(-2.1309, ERROR)); - assertThat(coeffs[1], closeTo(1.5213, ERROR)); - assertThat(coeffs[2], closeTo(-0.0373, ERROR)); - assertThat(coeffs[3], closeTo(0.0003, ERROR)); - } - - private double function(double x) { - return 0.0003*x*x*x-0.0373*x*x+1.5213*x -2.1309; - } - - - - -} diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java new file mode 100644 index 00000000000..f0172f92d17 --- /dev/null +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java @@ -0,0 +1,74 @@ +package com.sap.sailing.polars.regression.test; + +import static org.hamcrest.Matchers.closeTo; +import static org.junit.Assert.assertThat; + +import java.util.Random; + +import org.junit.Test; + +import com.sap.sailing.polars.regression.IncrementalLeastSquares; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; + +public class IncrementalLeastSquaresTest { + + + private static final double ERROR = 0.01; + + @Test + public void testIncrementalRegression() throws NotEnoughDataHasBeenAddedException { + IncrementalLeastSquares regression = new IncrementalAnyOrderLeastSquaresImpl(3); + long startTime = System.nanoTime(); + Random random = new Random(1); + for (int i = 0; i < 100000; i++) { + double x = random.nextDouble() * 40; + regression.addData(x, function(x) + (random.nextDouble() - 0.5)); + } + System.out.println("Adding Data " + (System.nanoTime() - startTime) + "ns"); + startTime = System.nanoTime(); + double[] coeffs = regression.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("First calculation " + (System.nanoTime() - startTime) + "ns"); + + startTime = System.nanoTime(); + coeffs = regression.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("No calculation " + (System.nanoTime() - startTime) + "ns"); + double x = random.nextDouble() * 40; + regression.addData(x, function(x) + (random.nextDouble() - 0.5)); + startTime = System.nanoTime() ; + coeffs = regression.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("Second calculation " + (System.nanoTime() - startTime) + "ns"); + + assertThat(coeffs[0], closeTo(-2.1309, ERROR)); + assertThat(coeffs[1], closeTo(1.5213, ERROR)); + assertThat(coeffs[2], closeTo(-0.0373, ERROR)); + assertThat(coeffs[3], closeTo(0.0003, ERROR)); + } + + private double function(double x) { + return 0.0003*x*x*x-0.0373*x*x+1.5213*x -2.1309; + } + + @Test + public void testIncrementalRegressionWithWebsiteExampleData() throws NotEnoughDataHasBeenAddedException { + IncrementalLeastSquares regression = new IncrementalAnyOrderLeastSquaresImpl(4); + regression.addData(-1, -1); + regression.addData(0, 3); + regression.addData(1, 2.5); + regression.addData(2, 5); + regression.addData(3, 4); + regression.addData(5, 2); + regression.addData(7, 5); + regression.addData(9, 4); + double[] coeffs = regression.getOrCreatePolynomialFunction().getCoefficients(); + assertThat(coeffs[0], closeTo(2.686, ERROR)); + assertThat(coeffs[1], closeTo(2.301, ERROR)); + assertThat(coeffs[2], closeTo(-1.233, ERROR)); + assertThat(coeffs[3], closeTo(0.2169 , ERROR)); + assertThat(coeffs[4], closeTo(-0.01182 , ERROR)); + } + + + + +} diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionOrderOneTest.java similarity index 55% rename from java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionTest.java rename to java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionOrderOneTest.java index 05823d6064f..b8bf40b0297 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLinearRegressionOrderOneTest.java @@ -7,149 +7,139 @@ import static org.junit.Assert.assertTrue; import org.hamcrest.number.IsCloseTo; import org.junit.Test; -import com.sap.sailing.polars.regression.IncrementalLinearRegressionProcessor; +import com.sap.sailing.polars.regression.IncrementalLeastSquares; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; -import com.sap.sailing.polars.regression.impl.IncrementalLeastSquaresProcessor; +import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; -public class IncrementalLinearRegressionTest { +public class IncrementalLinearRegressionOrderOneTest { private static double EPSILON = 1E-12; private static double VAGUE_EPSILON = 0.1; - private IncrementalLinearRegressionProcessor createRegressionProcessor() { - IncrementalLinearRegressionProcessor processor = new IncrementalLeastSquaresProcessor(); - return processor; - } - @Test public void testRegressionWithTwoDataPoints() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); - + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = 3; double y1 = 7; - processor.addMeasuredPoint(x1, y1); + leastSquares.addData(x1, y1); double x2 = 6; double y2 = 14; - processor.addMeasuredPoint(x2, y2); + leastSquares.addData(x2, y2); - double y = processor.getEstimatedY(x1); + double y = leastSquares.getOrCreatePolynomialFunction().value(x1); assertThat(y, new IsCloseTo(y1, EPSILON)); - y = processor.getEstimatedY(x2); + y = leastSquares.getOrCreatePolynomialFunction().value(x2); assertThat(y, new IsCloseTo(y2, EPSILON)); - y = processor.getEstimatedY(4.5); + y = leastSquares.getOrCreatePolynomialFunction().value(4.5); assertThat(y, new IsCloseTo(10.5, EPSILON)); } @Test public void testRegressionWithMultipleDataPoints() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); for (int i = -50; i < 51; i++) { - processor.addMeasuredPoint(i, i); + leastSquares.addData(i, i); } - double y = processor.getEstimatedY(-2); + double y = leastSquares.getOrCreatePolynomialFunction().value(-2); assertThat(y, new IsCloseTo(-2, EPSILON)); // 2 Points off the line - processor.addMeasuredPoint(-8, -3); - processor.addMeasuredPoint(8, -3); + leastSquares.addData(-8, -3); + leastSquares.addData(8, -3); - y = processor.getEstimatedY(-2); + y = leastSquares.getOrCreatePolynomialFunction().value(-2); assertThat(y, new IsCloseTo(-2, VAGUE_EPSILON)); - y = processor.getEstimatedY(20); + y = leastSquares.getOrCreatePolynomialFunction().value(20); assertThat(y, new IsCloseTo(20, VAGUE_EPSILON)); } @Test public void testRegressionWithABigAmountOfDataPoints() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); for (int i = -1000; i < 1000; i++) { // For every even number + 1, else - 1 double y = i % 2 == 0 ? i + 1 : i - 1; - processor.addMeasuredPoint(i, y); + leastSquares.addData(i, y); } - double y = processor.getEstimatedY(-2); + double y = leastSquares.getOrCreatePolynomialFunction().value(-2); assertThat(y, new IsCloseTo(-2, VAGUE_EPSILON)); - y = processor.getEstimatedY(20); + y = leastSquares.getOrCreatePolynomialFunction().value(20); assertThat(y, new IsCloseTo(20, VAGUE_EPSILON)); } @Test public void testRegressionWithZeroValueData() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = 0; double y1 = 0; - processor.addMeasuredPoint(x1, y1); + leastSquares.addData(x1, y1); double x2 = 5; double y2 = 0; - processor.addMeasuredPoint(x2, y2); + leastSquares.addData(x2, y2); - double y = processor.getEstimatedY(x1); + double y = leastSquares.getOrCreatePolynomialFunction().value(x1); assertThat(y, new IsCloseTo(y1, EPSILON)); - y = processor.getEstimatedY(x2); + y = leastSquares.getOrCreatePolynomialFunction().value(x2); assertThat(y, new IsCloseTo(y2, EPSILON)); } @Test public void testRegressionWithNegativeData() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = -5; double y1 = -5; - processor.addMeasuredPoint(x1, y1); + leastSquares.addData(x1, y1); double x2 = 5; double y2 = 5; - processor.addMeasuredPoint(x2, y2); + leastSquares.addData(x2, y2); - double y = processor.getEstimatedY(x1); + double y = leastSquares.getOrCreatePolynomialFunction().value(x1); assertThat(y, new IsCloseTo(y1, EPSILON)); - y = processor.getEstimatedY(x2); + y = leastSquares.getOrCreatePolynomialFunction().value(x2); assertThat(y, new IsCloseTo(y2, EPSILON)); - y = processor.getEstimatedY(0); + y = leastSquares.getOrCreatePolynomialFunction().value(0); assertThat(y, new IsCloseTo(0, EPSILON)); } @Test public void testRegressionWithSmallValuedData() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = 5E-9; double y1 = 5E-9; for (int i = 0; i < 1000; i++) { double yTimesI = y1 * i; double xTimesI = x1 * i; - processor.addMeasuredPoint(xTimesI, yTimesI); - if (i > 0) { - double y = processor.getEstimatedY(xTimesI); - assertThat(y, new IsCloseTo(yTimesI, EPSILON)); - } + leastSquares.addData(xTimesI, yTimesI); } // Add data that is off the line but offsetting each other - processor.addMeasuredPoint(-x1, y1); - processor.addMeasuredPoint(x1, -y1); + leastSquares.addData(-x1, y1); + leastSquares.addData(x1, -y1); - double y = processor.getEstimatedY(0); + double y = leastSquares.getOrCreatePolynomialFunction().value(0); assertThat(y, new IsCloseTo(0, EPSILON)); } @Test(expected = NotEnoughDataHasBeenAddedException.class) public void testNoDataExceptionThrowing() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); - processor.getEstimatedY(15); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); + leastSquares.getOrCreatePolynomialFunction().value(15); } /** @@ -159,13 +149,13 @@ public class IncrementalLinearRegressionTest { */ @Test public void testRegressionWithOneDataPoint() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor processor = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); double x1 = 3; double y1 = 5; - processor.addMeasuredPoint(x1, y1); + leastSquares.addData(x1, y1); - double y = processor.getEstimatedY(20); + double y = leastSquares.getOrCreatePolynomialFunction().value(20); assertThat(y, is(y1)); } @@ -196,37 +186,37 @@ public class IncrementalLinearRegressionTest { @Test public void testNorris() throws NotEnoughDataHasBeenAddedException { - IncrementalLinearRegressionProcessor regression = createRegressionProcessor(); + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); for (int i = 0; i < data.length; i++) { - regression.addMeasuredPoint(data[i][1], data[i][0]); + leastSquares.addData(data[i][1], data[i][0]); } // Tests against certified values from // http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Norris.dat - assertThat(regression.getSlope(), new IsCloseTo(1.00211681802045, EPSILON)); - assertThat(regression.getIntercept(), new IsCloseTo(-0.262323073774029, EPSILON)); + assertThat(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[1], new IsCloseTo(1.00211681802045, EPSILON)); + assertThat(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[0], new IsCloseTo(-0.262323073774029, EPSILON)); // ------------ End certified data tests - assertThat(regression.getEstimatedY(0), new IsCloseTo(-0.262323073774029, EPSILON)); - assertThat(regression.getEstimatedY(1), new IsCloseTo(1.00211681802045 - 0.26232307377402, EPSILON)); + assertThat(leastSquares.getOrCreatePolynomialFunction().value(0), new IsCloseTo(-0.262323073774029, EPSILON)); + assertThat(leastSquares.getOrCreatePolynomialFunction().value(1), new IsCloseTo(1.00211681802045 - 0.26232307377402, EPSILON)); } @Test - public void testPerfect() { - IncrementalLinearRegressionProcessor regression = createRegressionProcessor(); + public void testPerfect() throws NotEnoughDataHasBeenAddedException { + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); int n = 100; for (int i = 0; i < n; i++) { - regression.addMeasuredPoint(((double) i) / (n - 1), i); + leastSquares.addData(((double) i) / (n - 1), i); } - assertTrue(regression.getSlope() > 0); + assertTrue(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[1] > 0); } @Test - public void testPerfectNegative() { - IncrementalLinearRegressionProcessor regression = createRegressionProcessor(); + public void testPerfectNegative() throws NotEnoughDataHasBeenAddedException { + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(1); int n = 100; for (int i = 0; i < n; i++) { - regression.addMeasuredPoint(-((double) i) / (n - 1), i); + leastSquares.addData(-((double) i) / (n - 1), i); } - assertTrue(regression.getSlope() < 0); + assertTrue(leastSquares.getOrCreatePolynomialFunction().getCoefficients()[1] < 0); } diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java deleted file mode 100644 index 7ca7885dde3..00000000000 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/OnlineMultiVariateLinearRegressionTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.sap.sailing.polars.regression.test; - -import static org.hamcrest.Matchers.closeTo; -import static org.junit.Assert.assertThat; - -import java.util.Random; - -import org.junit.Test; - -import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; -import com.sap.sailing.polars.regression.OnlineMultiVariateRegression; -import com.sap.sailing.polars.regression.impl.OnlineMultiVariateRegressionImpl; - -public class OnlineMultiVariateLinearRegressionTest { - - private static double EPSILON = 1; - - @Test - public void testRegressionWithPolarData() throws NotEnoughDataHasBeenAddedException { - OnlineMultiVariateRegression regression = new OnlineMultiVariateRegressionImpl(3); - Random random = new Random(1); - - for (int i = 0; i < 10000; i++) { - double x = random.nextDouble() * 80 - 40; - double[] xArray = {x, x*x, x*x*x}; - regression.addData(function(x, random), xArray); - } - - double[] xArray = {5, 25, 125}; - double estimated = regression.estimateY(xArray); - - assertThat(estimated, closeTo(function(5, random), EPSILON)); - } - - private double function(double x) { - return 0.0003*x*x*x-0.0373*x*x+1.5213*x -2.1309; - } - - private double function(double x, Random random) { - return function(x) + random.nextGaussian() * 5; - } - -} diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java index 42b953c8aa9..3c1dfb68f7c 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetGenerationTest.java @@ -81,7 +81,7 @@ public class PolarSheetGenerationTest { double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. }; WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0); PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5, - 0.05, false, windStepping, false); + 0.05, false, windStepping, false, 0); TimePoint startTime = new MillisecondsTimePoint(9); TimePoint endTime = new MillisecondsTimePoint(80); @@ -121,7 +121,7 @@ public class PolarSheetGenerationTest { double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. }; WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0); PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 10, 0, false, true, 5, - 0.05, false, windStepping, false); + 0.05, false, windStepping, false, 0); PolarSheetHistogramBuilder builder = new PolarSheetHistogramBuilder(settings); diff --git a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF index 5b89143f5c8..77466719b0d 100644 --- a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF @@ -21,7 +21,8 @@ Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0", com.sap.sse.common, com.sap.sse, - com.sap.sse.shared.android + com.sap.sse.shared.android, + org.apache.commons.math;bundle-version="2.1.0" Import-Package: com.sap.sailing.util, com.sap.sse.common diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java index 3909b11201b..2e15631fc45 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java @@ -5,19 +5,21 @@ import java.util.Map; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.polars.regression.impl.AnyOrderLeastSquaresRegression; +import com.sap.sailing.polars.regression.IncrementalLeastSquares; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; import com.sap.sse.common.Util.Pair; public class CubicSpeedRegressions { - private final Map, AnyOrderLeastSquaresRegression> regressions; + private final Map, IncrementalLeastSquares> regressions; public CubicSpeedRegressions() { regressions = new HashMap<>(); for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { regressions.put(new Pair(legType, tack), - new AnyOrderLeastSquaresRegression(3)); + new IncrementalAnyOrderLeastSquaresImpl(3)); } } } @@ -39,17 +41,19 @@ public class CubicSpeedRegressions { legType = LegType.UPWIND; tack = Tack.STARBOARD; } - AnyOrderLeastSquaresRegression regression = regressions.get(new Pair(legType, tack)); - regression.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); + IncrementalLeastSquares leastSquares = regressions.get(new Pair(legType, tack)); + leastSquares.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); } public double estimateSpeedInKnots(LegType legType, Tack tack, double windSpeedInKnots) { - double[] coeffs = regressions.get(new Pair(legType, tack)).getCoefficiants(); - double resultSum = 0; - for (int i = 0; i < coeffs.length; i++) { - resultSum = resultSum + coeffs[i] * Math.pow(windSpeedInKnots, i); + double result; + try { + result = regressions.get(new Pair(legType, tack)).getOrCreatePolynomialFunction().value(windSpeedInKnots); + } catch (NotEnoughDataHasBeenAddedException e) { + //FIXME try to come up with something smarter + result = 0; } - return resultSum; + return result; } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index 95312f7740e..3ab5dd6958c 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -56,7 +56,11 @@ public class PolarDataMiner implements PolarSheetAnalyzer { private IncrementalRegressionProcessor incrementalRegressionProcessor; public PolarDataMiner() { - backendPolarSheetGenerationSettings = PolarSheetGenerationSettingsImpl.createBackendPolarSettings(); + this(PolarSheetGenerationSettingsImpl.createBackendPolarSettings()); + } + + public PolarDataMiner(PolarSheetGenerationSettings backendPolarSettings) { + backendPolarSheetGenerationSettings = backendPolarSettings; try { setUpWorkflow(); } catch (ClassCastException | NoSuchMethodException | SecurityException e) { @@ -81,7 +85,7 @@ public class PolarDataMiner implements PolarSheetAnalyzer { Collection> filteringResultReceivers = Arrays.asList(groupingProcessor); Processor filteringProcessor = new ParallelFilteringProcessor( - GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria(1)); + GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria(backendPolarSheetGenerationSettings.getNumberOfLeadingCompetitorsToInclude())); Collection> enrichingResultReceivers = Arrays .asList(filteringProcessor); diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java new file mode 100644 index 00000000000..57e3e4aca88 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLeastSquares.java @@ -0,0 +1,31 @@ +package com.sap.sailing.polars.regression; + +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + +/** + * Support incremental regression linear in one variable but potentially of higher order. + * + * @author Frederik Petersen (D054528) + * + */ +public interface IncrementalLeastSquares { + + /** + * Adds data to the least squares regression. This only updates the internal matrix and vector and will not trigger the + * actual least square solving. See {@link #getOrCreatePolynomialFunction()}. + * @param x + * @param y + */ + public abstract void addData(double x, double y); + + /** + * The polynomial function is cached and will be returned immediately if no data has been added since the last call. + * Otherwise the calculation is performed and the polynomial function is returned. It has a complexity of O(order) where + * order is the requested order of the polynomial. + * + * @return polynomial function estimating the data. Can be constant when only one data point has been added. + * @throws NotEnoughDataHasBeenAddedException Will be returned if no data has been added to the regression. + */ + public abstract PolynomialFunction getOrCreatePolynomialFunction() throws NotEnoughDataHasBeenAddedException; + +} \ No newline at end of file diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java deleted file mode 100644 index d4babfb0573..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/IncrementalLinearRegressionProcessor.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.sap.sailing.polars.regression; - -public interface IncrementalLinearRegressionProcessor { - - double getEstimatedY(double x) throws NotEnoughDataHasBeenAddedException; - - void addMeasuredPoint(double x, double y); - - double getSlope(); - - double getIntercept(); - - int getDataCount(); - - double getMeanOfY() throws NotEnoughDataHasBeenAddedException; - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java deleted file mode 100644 index 822a0229505..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/OnlineMultiVariateRegression.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.polars.regression; - -public interface OnlineMultiVariateRegression { - - /** - * Adds data to the regression. This will change the weights. - * - * @param y - * @param x - * length of the array needs to be the dimension of the regression (depending on the implementation, the - * dimension could be configured in the constructor), else IllegalArgumentsException will be thrown - */ - void addData(double y, double[] x); - - /** - * After adding some data with {@link #addData(double, double[])} this method can be used to estimate y for any - * x-vector. - * - * @param x - * length of the array needs to be the dimension of the regression (depending on the implementation, the - * dimension could be configured in the constructor), else IllegalArgumentsException will be thrown - * @return - */ - double estimateY(double[] x); - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java deleted file mode 100644 index 2c31df3bb44..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/AnyOrderLeastSquaresRegression.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.sap.sailing.polars.regression.impl; - -import com.sap.sse.concurrent.LockUtil; -import com.sap.sse.concurrent.NamedReentrantReadWriteLock; - -/** - * Thread safe implementation of the Least Squares regression for any order of result polynomial. - * - * {@link #addData(double, double)} will incrementally update the simultaneous equations needed for the calculation of - * the result coefficients. - * - * {@link #getCoefficiants()} will do the solving of the equations, if new data has been added since the last - * calculation. - * - * @author D054528 - * - */ -public class AnyOrderLeastSquaresRegression { - - private final double aMatrix[][]; - private final double bMatrix[]; - private final double term[]; - private final int nOrder; - - private boolean needsCoeffUpdate = true; - private double[] coeffs; - - private final NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("LeastSquaresLock", true); - - /** - * - * @param nOrder order of the polynomial you want to fit over the data. - */ - public AnyOrderLeastSquaresRegression(int nOrder) { - this.nOrder = nOrder; - aMatrix = new double[nOrder + 1][nOrder + 1]; - bMatrix = new double[nOrder + 1]; - term = new double[nOrder + 1]; - } - - /** - * Add data to the regression. This will NOT trigger a calculation of the coefficiants, but only update the matrices - * that form the simultaneous equations. - * - * Use {@link #getCoefficiants()} to retrieve the coefficiants of the fit. - * - * @param yValue - * @param xValue - */ - public void addData(double yValue, double xValue) { - LockUtil.lockForWrite(lock); - try { - needsCoeffUpdate = true; - // sum the y values - bMatrix[0] += yValue; - // sum the x power values - double xpower = 1; - for (int j = 0; j < nOrder + 1; j++) { - term[j] = xpower; - aMatrix[0][j] += xpower; - xpower = xpower * xValue; - } - // now set up the rest of rows in the matrix - multiplying each row by each term - for (int j = 1; j < nOrder + 1; j++) { - bMatrix[j] += yValue * term[j]; - for (int k = 0; k < bMatrix.length; k++) { - aMatrix[j][k] += term[j] * term[k]; - } - } - } finally { - LockUtil.unlockAfterWrite(lock); - } - } - - /** - * If the coefficiants have been requested before, and not data was added, the coefficiants will simply be returned. - * If data was added since the last time this method was called, the calculation will take place. - * - * The calculation is standard gaussian solving of the simultaneous equations. It has the complexity (nOrder + 1)^3 - * - * @return Coefficiants a[] of the polynomial function. Sorted by power => a[0]*x^0 to a[n]*x^n. - */ - public double[] getCoefficiants() { - if (needsCoeffUpdate) { - double[] coeffs = gauss(); - LockUtil.lockForWrite(lock); - try { - this.coeffs = coeffs; - needsCoeffUpdate = false; - } finally { - LockUtil.unlockAfterWrite(lock); - } - } - LockUtil.lockForRead(lock); - double[] result; - try { - result = this.coeffs; - } finally { - LockUtil.unlockAfterRead(lock); - } - return result; - } - - /** - * a standard gaussian technique for / solving simultaneous equations of the form: |A| = |B| * |C| where we / know - * the values of |A| and |B|, and we are solving for the coefficients / in |C| - */ - private double[] gauss() { - double aCopy[][] = new double[aMatrix.length][aMatrix[0].length]; - double bCopy[] = new double[bMatrix.length]; - double pivot; - double mult; - double top; - int n = bCopy.length; - double coef[] = new double[n]; - - // copy over the array values - inplace solution changes values - LockUtil.lockForRead(lock); - try { - for (int i = 0; i < aMatrix.length; i++) { - for (int j = 0; j < aMatrix[i].length; j++) { - aCopy[i][j] = aMatrix[i][j]; - } - bCopy[i] = bMatrix[i]; - } - } finally { - LockUtil.unlockAfterRead(lock); - } - - for (int j = 0; j < (n - 1); j++) { - pivot = aCopy[j][j]; - for (int i = j + 1; i < n; i++) { - mult = aCopy[i][j] / pivot; - for (int k = j + 1; k < n; k++) { - aCopy[i][k] = aCopy[i][k] - mult * aCopy[j][k]; - } - bCopy[i] = bCopy[i] - mult * bCopy[j]; - } - } - - coef[n - 1] = bCopy[n - 1] / aCopy[n - 1][n - 1]; - for (int i = n - 2; i >= 0; i--) { - top = bCopy[i]; - for (int k = i + 1; k < n; k++) { - top = top - aCopy[i][k] * coef[k]; - } - coef[i] = top / aCopy[i][i]; - } - return coef; - } - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java new file mode 100644 index 00000000000..06ac9410c4e --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java @@ -0,0 +1,133 @@ +package com.sap.sailing.polars.regression.impl; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; +import org.apache.commons.math.linear.LUDecompositionImpl; +import org.apache.commons.math.linear.MatrixUtils; +import org.apache.commons.math.linear.RealMatrix; +import org.apache.commons.math.linear.RealVector; + +import com.sap.sailing.polars.regression.IncrementalLeastSquares; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sse.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; + +/** + * This class implements the incremental polynomial regression approach described here: + * + * + * http://erikerlandson.github.io/blog/2012/07/05/deriving-an-incremental-form-of-the-polynomial-regression-equations/ + * + * + * @author Frederik Petersen D054528 + * + */ +public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSquares { + + private double[][] matrixOfXSums; + + private double[] vectorOfXYMultSums; + + private int polynomialOrder; + + private AtomicLong numberOfPointsAdded = new AtomicLong(0); + + private AtomicBoolean functionNeedsUpdate = new AtomicBoolean(true); + + private final NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("IncrementalLeastSquaresLock", + false); + + private final NamedReentrantReadWriteLock cacheLock = new NamedReentrantReadWriteLock("IncrementalLeastSquaresCacheLock", + true); + + private PolynomialFunction cachedFunction; + + public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder) { + this.polynomialOrder = polynomialOrder; + matrixOfXSums = new double[polynomialOrder + 1][polynomialOrder + 1]; + vectorOfXYMultSums = new double[polynomialOrder + 1]; + } + + @Override + public void addData(double x, double y) { + LockUtil.lockForWrite(lock); + try { + numberOfPointsAdded.incrementAndGet(); + functionNeedsUpdate.set(true); + for (int i = 0; i <= polynomialOrder; i++) { + vectorOfXYMultSums[i] += y * Math.pow(x, i); + for (int j = 0; j <= polynomialOrder; j++) { + if (i == 0 && j == 0) { + matrixOfXSums[i][j] += 1; + } else { + matrixOfXSums[i][j] += Math.pow(x, i+j); + } + } + } + } finally { + LockUtil.unlockAfterWrite(lock); + } + } + + @Override + public PolynomialFunction getOrCreatePolynomialFunction() throws NotEnoughDataHasBeenAddedException { + + PolynomialFunction resultFunction; + if (!functionNeedsUpdate.get()) { + LockUtil.lockForRead(cacheLock); + try { + resultFunction = new PolynomialFunction(cachedFunction.getCoefficients()); + } finally { + LockUtil.unlockAfterRead(cacheLock); + } + } else { + if (numberOfPointsAdded.get() < 1) { + throw new NotEnoughDataHasBeenAddedException("No points have been added to the regression yet."); + } + if (numberOfPointsAdded.get() == 1) { + resultFunction = createConstantFunctionForOneDataPoint(); + } else { + resultFunction = createEstimatingPolynomialFunction(); + } + LockUtil.lockForWrite(cacheLock); + try { + cachedFunction = new PolynomialFunction(resultFunction.getCoefficients()); + } finally { + LockUtil.unlockAfterWrite(cacheLock); + } + functionNeedsUpdate.set(false); + } + return resultFunction; + } + + private PolynomialFunction createEstimatingPolynomialFunction() { + PolynomialFunction resultFunction; + RealMatrix matrixOfXSumsCopy; + RealVector vectorOfXYMultSumsCopy; + LockUtil.lockForRead(lock); + try { + matrixOfXSumsCopy = MatrixUtils.createRealMatrix(matrixOfXSums); + vectorOfXYMultSumsCopy = MatrixUtils.createRealVector(vectorOfXYMultSums); + } finally { + LockUtil.unlockAfterRead(lock); + } + RealMatrix inversedMatrix = new LUDecompositionImpl(matrixOfXSumsCopy).getSolver().getInverse(); + RealVector coeffs = inversedMatrix.operate(vectorOfXYMultSumsCopy); + resultFunction = new PolynomialFunction(coeffs.toArray()); + return resultFunction; + } + + private PolynomialFunction createConstantFunctionForOneDataPoint() { + LockUtil.lockForRead(lock); + double[] coeff = new double[1]; + try { + coeff[0] = vectorOfXYMultSums[0]; + } finally { + LockUtil.unlockAfterRead(lock); + } + return new PolynomialFunction(coeff); + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java deleted file mode 100644 index 64b99193c44..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalLeastSquaresProcessor.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.sap.sailing.polars.regression.impl; - -import com.sap.sailing.polars.regression.IncrementalLinearRegressionProcessor; -import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; - -/** - * Allows a linear regression of a 2 dimensional and growing dataset. It has a complexity of O(1) - * - * The algorithm and variable naming is similar to the german wikipedia - * article - * - * The difference is that some parts of the equation are memorized as fields and updated incrementally. - * - * @author Frederik Petersen (D054528) - * - */ -public class IncrementalLeastSquaresProcessor implements IncrementalLinearRegressionProcessor { - - private double alpha1 = 0; - - private double alpha0 = 0; - - private double partA = 0; - - private double partB = 0; - - private double meanOfX = 0; - - private double meanOfY = 0; - - private int numberOfPointsAdded = 0; - - @Override - public double getEstimatedY(double x) throws NotEnoughDataHasBeenAddedException { - if (numberOfPointsAdded < 1) { - throw new NotEnoughDataHasBeenAddedException(); - } - - double result; - if (numberOfPointsAdded < 2) { - // Return the y value of the only added data point. - result = meanOfY; - } else { - result = alpha0 + alpha1 * x; - } - - return result; - } - - @Override - public void addMeasuredPoint(double x, double y) { - numberOfPointsAdded++; - - calculatePartA(x, y); - calculatePartB(x, y); - - addToArithmeticMeanOfX(x); - addToArithmeticMeanOfY(y); - - calculateFactor1(); - calculateFactor2(); - } - - private void calculateFactor2() { - alpha0 = meanOfY - alpha1 * meanOfX; - } - - private void calculateFactor1() { - double numerator = calculateNumeratorOfFactor1(); - double denominator = calculateDenominatorOfFactor1(); - if (denominator == 0) { - alpha1 = 0; - } else { - alpha1 = numerator / denominator; - } - } - - private double calculateNumeratorOfFactor1() { - return partA - numberOfPointsAdded * meanOfX * meanOfY; - } - - private double calculateDenominatorOfFactor1() { - return partB - numberOfPointsAdded * meanOfX * meanOfX; - } - - private void addToArithmeticMeanOfY(double y) { - meanOfY = addToMean(y, meanOfY); - } - - private void addToArithmeticMeanOfX(double x) { - meanOfX = addToMean(x, meanOfX); - } - - private double addToMean(double z, double oldMean) { - return oldMean + (z - oldMean) / numberOfPointsAdded; - } - - private void calculatePartB(double x, double y) { - partB = partB + x * x; - } - - private void calculatePartA(double x, double y) { - partA = partA + x * y; - } - - @Override - public double getSlope() { - return alpha1; - } - - @Override - public double getIntercept() { - return alpha0; - } - - @Override - public int getDataCount() { - return numberOfPointsAdded; - } - - @Override - public double getMeanOfY() throws NotEnoughDataHasBeenAddedException { - if (numberOfPointsAdded < 1) { - throw new NotEnoughDataHasBeenAddedException(); - } - return meanOfY; - } - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java deleted file mode 100644 index fc2698548cf..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/OnlineMultiVariateRegressionImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.sap.sailing.polars.regression.impl; - -import com.sap.sailing.polars.regression.OnlineMultiVariateRegression; - -/** - * Allows online regression for multivariate linear functions as explained in: - * - * http://people.cs.pitt.edu/~milos/courses/cs2750-Spring03/lectures/class6.pdf (especially: Page 8+9) - * - * @author Frederik Petersen (D054528) - * - */ -public class OnlineMultiVariateRegressionImpl implements OnlineMultiVariateRegression { - - private double[] weights; - - private int numberOfAddedPoints = 0; - - private final double[] means; - - private final double[] sumsOfSquaresOfDifferencesFromMean; - - private final double[] variances; - - public OnlineMultiVariateRegressionImpl(int numberOfDimensions) { - weights = initializeDoubleArray(numberOfDimensions + 1); - means = initializeDoubleArray(numberOfDimensions); - sumsOfSquaresOfDifferencesFromMean = initializeDoubleArray(numberOfDimensions); - variances = initializeDoubleArray(numberOfDimensions); - } - - private double[] initializeDoubleArray(int arrayLength) { - double[] weights = new double[arrayLength]; - for (int i = 0; i < weights.length; i++) { - weights[i] = 0; - } - return weights; - } - - @Override - public void addData(double y, double[] x) { - checkNumberOfDimensions(x); - numberOfAddedPoints++; - double[] newWeights = initializeDoubleArray(weights.length); - double alpha = 0.005; - newWeights[0] = weights[0] + alpha * (y - estimateY(x)); - for (int i = 1; i < weights.length; i++) { - incrementallyUpdateVariances(x, i); - double normalizedInput = normalizeInput(x, i - 1); - newWeights[i] = weights[i] + (y - estimateY(x)) * Math.pow(alpha, i + 1) * x[i - 1];// normalizedInput; - } - weights = newWeights; - } - - private double normalizeInput(double[] x, int dimensionIndex) { - double normalizedInput; - if (variances[dimensionIndex] > 0) { - normalizedInput = (x[dimensionIndex] - means[dimensionIndex]) / Math.sqrt(variances[dimensionIndex]); - } else { - normalizedInput = x[dimensionIndex]; - } - return normalizedInput; - } - - private void incrementallyUpdateVariances(double[] x, int i) { - //http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Incremental_algorithm - double oldMean = means[i-1]; - means[i-1] = means[i-1] + ((x[i-1] - means[i-1]) / numberOfAddedPoints); - sumsOfSquaresOfDifferencesFromMean[i-1] = sumsOfSquaresOfDifferencesFromMean[i-1] + (x[i-1] - oldMean) * (x[i-1] - means[i-1]); - variances[i-1] = sumsOfSquaresOfDifferencesFromMean[i-1] / numberOfAddedPoints ; - } - - @Override - public double estimateY(double[] x) { - checkNumberOfDimensions(x); - double resultSum = weights[0]; - for (int i = 1; i < weights.length; i++) { - double normalizedInput = normalizeInput(x, i - 1); - resultSum = resultSum + (weights[i] * x[i - 1]); - } - return resultSum; - } - - private void checkNumberOfDimensions(double[] x) { - if (x.length + 1 != weights.length) { - throw new IllegalArgumentException("Dimension of x needs to be the same as specified in constructor."); - } - } - -} diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target index fc3bc7a06be..e57a3255ffb 100755 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target @@ -47,6 +47,7 @@ + @@ -103,6 +104,7 @@ + From 4a030a33d30f595f53346ab4859f581ac6286fdf Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Tue, 17 Feb 2015 13:55:18 +0100 Subject: [PATCH 142/264] IncrementalLeastSquares now has noIntercept Option --- .../5O5Worlds14_Winners_Upwind4550.csv | 8620 +++++++++++++++++ .../test/IncrementalLeastSquaresTest.java | 63 + .../IncrementalAnyOrderLeastSquaresImpl.java | 48 +- 3 files changed, 8722 insertions(+), 9 deletions(-) create mode 100644 java/com.sap.sailing.polars.test/resources/5O5Worlds14_Winners_Upwind4550.csv diff --git a/java/com.sap.sailing.polars.test/resources/5O5Worlds14_Winners_Upwind4550.csv b/java/com.sap.sailing.polars.test/resources/5O5Worlds14_Winners_Upwind4550.csv new file mode 100644 index 00000000000..6abb4a4f5c6 --- /dev/null +++ b/java/com.sap.sailing.polars.test/resources/5O5Worlds14_Winners_Upwind4550.csv @@ -0,0 +1,8620 @@ +14.711552847861,6.94618228340744,-46.9340311996113 +14.6159918588954,6.54088070915254,-49.2612330883498 +14.572846357584,6.5026396664646,-46.8965213311156 +14.7444353798923,6.9322874671299,-47.1460377445613 +14.7365152168546,6.81940194001346,-49.836451949315 +14.859604607974,6.93289940368124,45.8738398488157 +12.8846305622534,7.19750052494341,46.5482238395806 +12.9710980182519,7.10907976170334,45.5744818336029 +12.7902426421164,7.40856352137361,46.2413323683829 +12.6793226254565,7.35335454068788,45.6104260345802 +14.4774114798141,6.85942658894508,45.9029742547059 +14.5252350152175,7.2230597061005,47.6681310665996 +14.5871031786617,7.37289061496789,48.5744103246467 +14.6690932641615,7.62430743906014,47.6657625188325 +14.6440907184735,7.53808866953584,47.9345620313256 +14.6725471186446,7.61941753496267,47.1864871387864 +14.5435407179465,7.23234736683786,45.8799011340583 +14.4476062246182,7.24133037922224,46.4747752203243 +14.3848711890206,7.31626756891012,47.934800688602 +14.298314277298,7.37882135938686,49.6332501084556 +14.2131158254716,7.87177889358349,48.366883048611 +14.0223385434789,8.11096304819554,47.8694995696142 +13.9316639443497,8.16773685988305,47.5673400911351 +14.1288090960312,8.02543803245828,47.8143830690836 +13.8265008813489,8.20702949224399,47.0528645243424 +13.3205511061634,7.74012536545492,48.0272724178019 +13.2282835167981,7.59319600041407,46.9131343824917 +13.1913069173927,7.54973352834865,47.3348067179067 +13.1841373584715,7.52228965193328,48.3124803358394 +13.1603164862826,7.48079260089842,48.5297198747742 +13.2548802673001,7.67808163296939,47.3589461623096 +13.1701369448872,7.48916982215104,49.8534748409756 +13.3104767086679,7.58377755479461,47.6711553914288 +13.1941730703085,7.5852898499925,49.6931717846005 +13.2627487428044,7.62711139030267,49.3492528590274 +13.3168810746174,7.53778753804486,47.7906594769404 +13.3917871530067,7.28697411658421,47.5401220733087 +13.3377053761841,7.46523241789488,48.3477535088292 +13.4090245128513,7.33927544385389,48.5329545445379 +13.3540379070854,7.37084903078733,47.7885198834288 +13.4336471240867,7.45364121539626,49.012524828009 +13.4249819954801,8.03096494195981,47.5696822249686 +13.4179809913144,7.91872599527293,48.4721319254204 +13.4179141511608,7.56075064782774,48.8985621556744 +13.4232659050506,7.75999963617433,47.9305235866418 +13.485750959733,7.99079110002143,45.9824161006373 +13.5152791758323,7.88919678185287,45.3475195495668 +13.4551061813851,8.0709073530278,46.7738364443917 +13.4971310135415,7.61468155991288,45.0982504994909 +13.3903153624206,6.86671341588622,45.9386574705885 +13.4353868702123,7.25144578172176,45.3480481184137 +13.3776476128705,6.5149128644851,48.0376223672559 +13.587874997171,6.94626199645777,48.771044908741 +13.5177502054129,6.87471832589357,49.57668498774 +13.6705548287336,6.93858713293373,48.5396930010053 +13.8120704074811,6.95252509016923,47.6362735225956 +13.8627831928314,6.89186960263324,47.0777962611198 +13.7688647890332,6.92990410654981,48.0807577260838 +13.9128653391672,6.83694402383334,46.1311599373438 +13.9637648618027,6.81232454249628,45.3582707399308 +13.954161889712,6.8938016175606,45.2152939191707 +13.9412049508927,7.04471679995682,49.7736887171483 +13.6587613839438,6.42586570325846,48.4732456985365 +13.4643032357113,6.27635073418743,46.9857673657815 +13.5415613020495,6.32663463334353,47.7620131218719 +13.430860347109,6.26033546826343,46.0203338673301 +13.3835202726599,6.28591106006682,46.5056597704459 +14.5953705572204,6.85712505176878,-46.0829246846163 +14.7649301317119,7.16860163493274,-49.2556374620174 +14.7498147848315,7.20443968485969,-48.5892917005523 +14.7213305868838,7.19471006764918,-48.5994801193325 +14.6586114611997,7.12690397125667,-47.5538873657252 +14.6057701803754,7.16534471763984,-47.0098565437128 +14.5341737582783,7.05316891140007,-46.6085509659574 +14.4278908256173,7.05454222894002,-45.8163075935542 +14.3457111705823,7.05882876643689,-46.0952119100189 +14.2386163885391,7.12682558029447,-45.4888153898683 +14.7295031850289,7.13314997303313,-48.8124801253549 +14.6413492038667,7.02869379610399,-47.3624497191194 +13.1835601847586,6.86692968583657,-45.6332289094956 +13.2293310275873,6.66178172901478,-46.6563366646234 +13.2831417554694,6.62148656221257,-46.8043329997902 +13.3152947218707,6.68065981993835,-47.7274723310016 +13.296591963554,6.5604981311684,-47.5170254048104 +13.4096423821086,6.95451507003495,-47.8334468567109 +13.5025730608028,7.06362292257368,-47.9299767281273 +13.6188033329141,7.1715187154922,-47.9458687072686 +13.8857449274808,7.21246216149313,-48.9043944902334 +13.7588121735686,7.14531368142722,-48.4721247946916 +13.9868979893651,7.15777459665182,-49.7513260721817 +14.3042984425327,7.43301025746943,-49.8781295086319 +14.3108997765479,7.47228444897473,-49.2701944502014 +14.3133963979744,7.51166742003783,-48.4367527681516 +14.2725693624838,7.68542707423661,-47.8958006323538 +14.1950479360709,7.28893908453086,-47.7504230593656 +14.0599863033057,7.15816195881581,-47.7021498369124 +14.0078196936853,7.12005536095119,-47.3848195634394 +14.1654611126178,7.0251112398412,-46.4190270103137 +14.1147051075126,7.0328783046043,-46.9269877856723 +13.7138705897593,6.98831239871708,-46.432532775534 +13.3016184523348,6.8213111141013,-46.1793467664636 +13.5778806743806,6.94370677877869,-45.8984039251234 +13.2284629736196,7.00667647914802,-45.433167565947 +13.4313160379083,6.91956052807993,-46.3021967415093 +13.2002372382511,7.10009549061528,-46.1465869717252 +13.1855942659699,7.00963952937872,-45.5845836628178 +13.2177207191877,6.97262404118084,-47.2637503467996 +13.2189525709183,7.03980177229186,-47.0169517682449 +13.1910991529492,6.94377294066074,-47.9383852576578 +13.1040522554119,6.99784442771997,-49.3114730527894 +13.1650583175154,7.03488953496435,-49.6489077931608 +12.9140463921829,6.76008417949426,-48.0894284142373 +13.0149816458219,7.00595081141863,-48.7732763914829 +12.9478076962767,6.79045017937601,-48.0367806578975 +12.8453868763154,6.8685489543064,-45.9327082723102 +12.8467783807393,6.77407782085816,-45.4923230336672 +12.871582747855,6.96266251248797,-46.4306691952473 +12.8637702014519,7.35971398265981,-45.5640272716167 +12.8676537312542,7.33259541968174,-45.6918915236947 +12.9016813289435,7.2666689287084,-46.9136121344247 +12.8574732168599,7.21900003961627,-45.7384911452388 +12.94594865354,7.10943776804579,-47.5737907094012 +12.9830870364918,6.88301018730776,-49.4937084785899 +12.9932891129371,6.8237333806282,-49.0934943784602 +13.2194902350466,6.63775778639742,-48.4956865888111 +13.334754309999,6.44937528803907,-47.0947758136156 +13.2717327700127,6.52776021029062,-47.7239610296339 +13.4140386747454,6.41135211561589,-46.0760091237819 +13.4908339054426,6.21995405216969,-45.5059347712633 +13.6808837229007,6.63931003087858,-49.5258187410111 +13.5422758178355,6.6972935253648,-47.3673770297868 +13.6197042848199,6.67457057233847,-47.8948241872071 +13.838789665176,6.08508047966845,-48.2613929426159 +13.9515322171774,6.11987340820209,-46.240985044909 +14.6063490765376,7.04426287938917,-47.9991207182136 +14.7403057912027,7.31430258551916,-49.73573548114 +14.6643520689918,7.19159720683108,-49.3886223473704 +14.8284199155944,7.31803606245404,-48.2490156756994 +15.029812721723,7.32385282887637,-45.314060833098 +15.4509703643605,7.50807668635425,-45.5198424700044 +15.5337189020774,7.5375504303152,-45.9088186467321 +15.587481790515,7.57047094954184,-46.2143442999794 +15.6333964423647,7.53461220663252,-45.5331016818681 +15.6384545796223,7.09473926966968,-45.3969655193814 +15.6162886721351,7.05164566528859,-45.6370459942958 +15.5894592372368,7.07419357414695,-46.1463482866243 +15.5592504612216,6.97693234003942,-46.5236378339546 +15.6162883221431,7.13219795535457,-45.9401333694421 +15.5384044189672,7.31338767682532,-45.045805725608 +15.4604996031454,7.3883042726355,-45.2242616643834 +15.3493852726577,7.5048476338701,-45.0320368193982 +15.2704137404386,7.52285530178016,-45.3711757981636 +15.2278871755199,7.59413577447291,-45.9539699904133 +15.2737175359708,7.0317726970036,-47.0139710894914 +15.233350281191,7.21560000783488,-46.8232342926184 +15.2735285135603,7.10306607397733,-47.2726424391102 +15.2646318072471,7.10775666627298,-47.7039485330362 +15.2631827276419,7.1674959006977,-47.7511831129844 +15.1785074448622,7.18347443327854,-47.5642660608181 +15.2188713912465,7.12777345243463,-47.4344511548554 +15.2318691853144,7.18297873856512,-47.6275221538546 +15.2520220575431,7.21817116226124,-47.4368050946444 +15.2625840576869,7.27319395988313,-47.3175710957428 +15.2693757150283,7.41970263611756,-46.7989580777256 +15.1629135466246,7.65241644258317,-46.325557187646 +15.1265850063555,7.64124769286392,-45.6421962374348 +15.2268472474199,7.47482462886036,-46.8128798681359 +15.2099576283779,7.53900683718088,-46.7673691481655 +15.0795696158007,7.30062878985305,-49.4035032260771 +14.4456792095225,6.31807200619612,-47.2020786600334 +14.5103869953463,6.17173385520138,-49.5502856023752 +14.2283190506564,6.59975734675628,-45.4885337004087 +13.6868866837931,7.42632748659588,-45.105999877109 +13.7115307916983,7.49614725940392,-47.2224881778091 +13.7566997815598,7.48826953192901,-48.8646878777822 +13.7327909855121,7.01807295090209,-49.9548586222077 +13.7466241748613,7.14044434801785,-49.9150746750602 +13.721234523127,7.41612190694375,-47.934953175104 +13.7413830811542,7.27048868464273,-49.3687745080594 +13.6641200932172,7.31430120976571,-46.8003561279305 +13.4890044348565,7.05426518328569,-48.6515286420938 +13.5461476719113,7.11070254291513,-47.5182626663723 +13.61022136266,7.23913437476483,-47.1255694200099 +13.0018546438582,7.15669702872004,-48.7072159298528 +12.9584753746032,7.26112628582555,-47.6561832925196 +12.9579485421835,7.31482188702569,-46.2297876125993 +12.8796084464971,7.08067503355049,-46.1399913529845 +12.9015688884354,6.90436035616661,-47.0918870264287 +12.9304681021657,6.68747155419885,-48.1317779044753 +12.9597932242813,6.63551526842123,-48.7453943266286 +12.9813788700025,6.57410068858411,-48.4505808321224 +13.0144706410517,6.63481700530826,-48.685332426311 +13.0371348582893,6.75244327325197,-47.0827523285967 +13.0371361944098,6.69309522888522,-46.4304304898026 +13.109948167039,6.69436207457333,-48.2215509897433 +13.0564971935708,6.75675576619237,-47.8239363576636 +13.1714861090192,7.27990946218518,-49.3925271351103 +13.1561967234659,7.40356080758035,-47.6884346147856 +13.5825607436908,7.12759490057729,-45.3375300918372 +13.6757047615512,6.59683058167509,-47.9313508547503 +13.9662046235651,6.31800284930556,-49.418726063111 +13.8294930481737,6.5074163164671,-49.6287345538971 +14.1848942405178,6.25013622044927,-49.9492041390762 +14.591748554489,6.53614262085633,-48.6217721658753 +14.7499874486082,6.71694036040075,-49.1854024245538 +14.8766694107694,6.8410792443764,-48.2092599572128 +15.0071314508536,6.94010016465711,-48.511966676181 +15.1229118048438,6.99183723194362,-49.2629565656183 +15.6471629534684,6.82432735679438,-46.8015296526396 +15.7190410724209,6.91941700248639,-48.2696641545066 +15.5848183548721,6.75797523136206,-46.9157977760487 +15.3765636822159,6.62371841360385,-47.7034837059605 +15.4869315521833,6.6397974524847,-47.6372693395932 +14.8170869023942,6.49509464953368,-49.6750709470182 +15.2747810069402,6.61977292022613,-48.2030238706527 +15.1696187542792,6.51732583285767,-49.2886911941079 +15.0587639221396,6.57748668004891,-48.5238666111601 +14.9197389936205,6.59567679210884,-48.3206847591833 +13.8632676039681,6.71414289332069,-49.8916385341516 +13.8995790628321,6.85144485267939,-49.2600093273842 +12.404977919362,6.65235510891555,45.5347728230634 +12.3801489807265,6.65627258208074,45.8041038829186 +11.8917178310978,6.05660357353104,49.1752265706924 +12.6677047735165,6.21829061948803,49.6906075578739 +12.7737515246081,6.25928650410134,49.7965121526927 +12.6185364896315,6.81684620803896,49.4076774323347 +12.6475262065448,6.848079938338,49.6820279325123 +12.6654052223146,7.05113947496727,46.5752258004084 +12.5768235637676,6.71705278326496,-48.1834090219133 +12.8238447902133,6.75504830768144,-46.1554061314817 +12.8600167715779,6.70949596657113,-45.9144201879356 +12.7624227042745,6.7611275306046,-45.8100988343795 +12.8711032353672,6.6950120494505,-47.5129694980938 +12.8762230198742,6.75508455128574,-46.6071520656831 +12.8230266908866,6.99212053438032,-45.7600542800887 +12.8288439669796,7.02945826723096,-46.0454545670272 +12.8288792319858,7.02998902044757,-45.572528592492 +13.4359834774773,6.894545638977,-47.473170692104 +13.5113742193938,6.93829300266345,-47.9646703369841 +13.744489679729,6.95893098232894,-49.3021094452656 +13.5827796111933,6.9189215222822,-49.124383178441 +13.6681707786627,6.89525162109361,-49.9152401061033 +14.3253046437324,7.09162716456454,-46.1246069748079 +14.5334425351666,7.15796713063406,-46.1674480148918 +14.4654470987262,7.13043110325981,-47.6203683032456 +14.4114698826285,7.03539789557043,-48.7602174536877 +14.8906132107518,7.28047844494593,-45.2938572828548 +14.9500549947699,7.27342285849742,-46.1904269031452 +15.0489205676389,7.31049291491253,-46.9503244719038 +15.0243649979588,7.2864790837202,-46.2851702602222 +15.0826044667176,7.34704138861661,-49.353988164794 +15.0815228444912,7.40825683047985,-48.0872337223198 +15.0776583396078,7.48654725753435,-46.8759451440999 +15.0845790344323,7.58262346849416,-46.1400969166312 +15.267442593594,7.62678606470899,-46.1289481612869 +15.3812762444534,7.62805268091487,-47.5284712332699 +15.5869267650758,7.40040610475065,-49.5735648687691 +15.5174082102141,7.53522247046115,-49.4588672585091 +15.5773599137223,7.2745780695025,-49.9710116446288 +15.6129438571348,7.26298806446619,-49.2174953725039 +15.6483929370885,7.24674332796299,-48.2069474776499 +15.7108264951503,7.30033221120015,-47.7689898291797 +15.7671579851365,7.48281923125894,-49.2751511049817 +15.7307341874833,7.59392726606696,-46.3732707787552 +15.7436811581868,7.60258451644006,-47.7795768746362 +15.4588548333764,7.87409375429705,-45.4412318677832 +15.3869526728951,7.85906570158903,-46.7198053120556 +15.3034282549769,7.9602757988749,-46.1142165707107 +15.2554974041565,7.94117109052962,-45.7323572464171 +15.2114064112272,7.84248270819316,-45.2388971218695 +15.1658190122156,7.85419949954236,-45.0808525104715 +15.2897965851397,7.91540931260313,-45.4097495599005 +14.9833841545024,7.7876514826042,-49.86614001065 +15.0971690301096,7.76538675712188,-47.9641215507222 +14.1217137483992,7.93277783902597,-49.3019651913443 +14.3390070104803,8.02407857775748,-49.0982971612203 +15.060004235812,8.10471311875845,-46.9144573729985 +15.1061173910151,8.09419537616321,-45.1201762740492 +15.0047547835928,7.70839817871016,-45.8665442570716 +15.0386179700666,7.77991517602384,-45.523447889705 +14.9739499280467,7.64938302183054,-45.5847319884975 +14.9765264513354,7.58392996644158,-45.027650599265 +15.0030130042239,7.39587571941684,-49.3062259855153 +15.0246829635027,7.17750248759549,-48.1129126130234 +15.028681111368,7.1153678094697,-47.0643382826768 +15.0617933882764,7.24545988130944,-49.4328899850541 +12.2306386051028,7.89395114390148,48.9461875234444 +12.0340505661188,7.92903242373883,49.3730502490857 +12.1770960646286,7.89952597399336,49.7374745867589 +12.1051301700528,7.92360841881134,49.2071564283372 +11.9752075892959,7.92841623508044,49.8262476740069 +11.9431590303889,7.92579967725081,46.0279400526771 +12.6227458681005,6.24517401899082,-45.473200574421 +12.6847841676021,6.35754779406096,-49.8176276402118 +12.9973760771393,7.01186882448989,-48.9499221228461 +13.3715900452409,6.60167928773874,-47.4664870202181 +12.9429123732044,6.64654515830395,-48.9386056569546 +13.354519409844,6.61822092003415,-47.0739726901748 +13.2710511235493,6.59306859291806,-45.5213868703125 +13.3233751011859,6.53468922403778,-46.4339202211515 +13.0233619429925,6.77249036383824,-45.5571403370861 +12.9389357794915,6.84476092652796,-46.4824129078599 +12.837947159763,6.90258252918889,-46.7808068986242 +12.7156106148799,7.09650692793771,-47.3363503637389 +12.467818373814,7.32517890083306,-46.9712013603837 +12.5783747361823,7.25977929505245,-47.4872906871429 +12.3694276059204,7.35334542036259,-47.000021718735 +12.3032301586987,7.4961819157028,-46.8988975059103 +12.2580494339561,7.32896521162645,-46.4156287274912 +12.2329931555151,7.31669512440123,-48.7022434560519 +12.1686899074415,7.1823558956209,-47.4888833038057 +12.1537055284882,7.24752510912063,-48.7146381844803 +12.1731929729351,7.21902067026705,-46.035538779698 +12.1700090743165,7.24531641519097,-46.9681141618943 +12.163695171893,7.30220975890532,-46.2902735808173 +11.199343438102,7.20583044295896,-47.130453521403 +11.1193785963718,7.12379963297734,-45.5554910250023 +10.5588745559977,6.70480458982114,-45.596701561023 +10.5238274247705,6.5824053827557,-45.560733685325 +10.5075303517988,6.56522019018991,-45.3675346223885 +11.0067745379264,6.43901248797274,-47.2687896402243 +10.9254017678361,6.50430715527373,-45.6742323572842 +11.0860174407236,6.2630668260237,-49.0628870265931 +11.2758594138556,6.20202996195265,-47.7150417469543 +11.1563619310433,6.24450631316083,-48.409579605363 +11.4428963682184,6.17940012695773,-45.6686558493989 +12.4133122985361,6.54936369863328,-45.4490700653168 +12.5375371962072,6.67987806190764,-45.3146062120132 +12.6994087196443,6.81170790297343,-45.4559133287821 +12.6296685213439,6.79821348146887,-45.0909248131638 +12.7102618948695,6.75545444276341,-45.4592420480073 +12.6646607696265,6.69329007368221,-47.3658154017329 +12.5881489955463,6.56268076183713,-48.1937000454791 +12.4218830995741,6.37609285388001,-48.4700181039778 +12.3675583930644,6.23266568051814,-48.796224410641 +12.4866736571346,6.45180545722542,-49.2609452302725 +12.3388376515066,6.09513424385224,-48.7793044253394 +12.3331252351487,5.91976891539028,-48.2303937229552 +12.2161264976244,5.80154322230025,-47.9816844329467 +12.2582302790877,5.90132888689207,-48.5936897749184 +12.1605984945931,5.63274198436091,-47.5439535256859 +11.9012532380336,4.93706579786348,-47.3102732145832 +11.7824896558941,4.89520123849967,-46.3518359250264 +12.0345301539331,5.06504360006739,-47.7901725301013 +11.6459769019182,4.86005178399657,-46.5223710077186 +11.436756974198,5.2149122521204,-45.34306055518 +11.4704521381331,5.31202624383632,-47.3091729671047 +11.5686204312178,5.35381534850587,-48.8370367682324 +11.9012556277369,5.12044711226185,-46.4685930816304 +11.6822836224183,5.26804429306482,-47.9345235542336 +11.7959748961933,5.21842752435636,-47.0886786160544 +12.020421888591,5.15061206299556,-46.3901935660715 +12.1478549194643,5.1436930306306,-45.6646584901816 +11.7044283502439,5.48816079014622,-45.9397598489062 +11.5398179155825,5.33651621725464,-45.401579118318 +11.6307052700703,5.37066217759708,-46.1167779812245 +10.5325178794661,5.87932444373674,-45.8029280197017 +10.6346656890565,5.88986613417924,-45.5527749373304 +10.3747880743364,6.55923157877322,-48.459207381755 +12.9193588357234,6.82307922031555,48.817188342488 +12.9230625232113,6.53670020745292,47.5113334075808 +12.9281189806034,6.41271017663053,47.5812590858073 +12.9413517973302,6.22245666435608,46.8974785916035 +12.9436629573326,5.98801946281497,45.4634195026869 +13.0287771937712,5.61356628571663,48.2534797387981 +13.15959440014,5.83536256132368,48.9217907558271 +13.0140215390148,5.60333327718287,47.5969346844768 +13.0801901593605,5.68475535429764,48.9774818552235 +13.2040863698572,5.85564094322689,45.7887175491317 +12.8667437706901,5.70140312114528,45.0848155382502 +12.9775051364043,5.76395368870932,45.3041358663923 +12.8046206705256,6.0561217832098,48.0610178058477 +12.7696929995046,6.12407936067944,48.6272668149565 +12.6338150122335,6.29875351956607,48.4712567489507 +12.6448017226106,6.30968091916708,48.2050564383395 +12.7156375635223,6.26806219290099,48.6953473806275 +12.6506525349602,5.9175809786141,48.531753439373 +12.644132106089,5.6364967900919,49.1235661462069 +12.6431965840304,5.7286705218746,49.3372603226211 +12.631381521235,5.56980349914976,49.265269639712 +12.5735361223676,5.61447647304748,48.2436799664376 +12.5646307790017,5.92136104980955,47.1056017191253 +12.6590875015439,6.14503367031352,49.1149034036707 +12.584739018472,5.98314570755475,48.8352788508365 +12.9262899006808,5.56519164650131,46.2745937616238 +11.96479166415,6.39977771662052,-49.371472671434 +12.1692675955274,6.34589792610899,-46.4064079311609 +12.0411262601237,6.38609363126879,-48.114550718744 +11.5904329176218,6.44087065406149,-49.9502379400151 +11.5860311839567,6.55395903044081,-47.5494919186564 +11.6065518582997,6.68179116367616,-45.6841872673374 +12.790873880634,6.4235135597154,48.6769832644915 +12.6501292179139,6.40092908877314,48.8248279976286 +13.5473045020963,7.47190911974559,49.592935639906 +13.6012487038201,7.59829796888071,49.0847338937798 +13.6490599005934,7.64680754991332,48.9811762128448 +13.7009492107932,7.69315694931139,48.5619742857834 +13.8034589583566,7.4625109220587,46.9761868523208 +13.8348406496215,7.58722152803218,46.6790744932307 +13.7679124629624,7.48573699851039,47.5188451511194 +13.7952114642481,7.48110135047142,47.3650134848473 +13.8173832258847,7.52767751047908,46.5428271599964 +13.7806876361239,7.38516317719336,48.4486995015173 +13.8220620505937,7.41725001815485,47.0354566517927 +13.8175581373906,7.3932993552293,46.7396442007689 +13.6926216592089,7.38984184818135,48.4934474729608 +13.7214884606766,7.36118139448745,47.7121224903782 +13.6478451600662,7.69207912065963,47.834535652255 +13.6159610995884,7.77847710105306,46.6353985381099 +13.6023534036337,7.83383648428792,46.2204873659886 +13.3747724542501,7.94091727121758,45.5725351391117 +13.2767675364189,8.03794144950192,46.8877321396606 +13.2944308043535,8.01040021991424,46.4849461812845 +13.3022650007327,8.17639251325926,48.9205992196074 +13.2751983087689,8.13615373723926,47.8459435642243 +14.2875886814618,9.00765130940429,49.3389759399447 +14.4327147544483,9.21969688008126,49.7869181104999 +15.9701051372785,8.21477922524831,49.7668092266216 +15.9904996481756,8.3002140059692,47.866515619659 +15.9903421156477,8.37270970100538,46.9679716062477 +15.8921708888226,8.56461066357729,49.8407705613099 +15.9605688922479,8.61498512218061,48.3548314602099 +15.9420219677407,8.58597436442098,49.1418767259175 +15.8117371942247,8.33303687905251,49.5676144791914 +15.835485291999,8.48137240713948,48.7018250656498 +15.7862962186312,8.2418700458039,45.976277087418 +14.4340175455992,8.25743746465377,45.5722313298047 +14.4056502808149,8.32124895423551,45.3355901088911 +15.1945968557163,8.11901600659021,45.0044558957076 +15.2525416504869,7.9937014708694,45.6715098729898 +15.3408738226935,7.78687917376312,45.6539539384234 +15.4329870660995,7.75494367843455,46.6777519539866 +15.4009608331217,7.71589009710907,46.475611602404 +15.4007714454253,8.25342627189963,45.8083651461499 +15.3696840499922,8.67971349591705,45.1357823038332 +15.3480400839854,9.03901426368719,45.0139367194123 +15.3661376757315,8.87743276426036,45.1509320366228 +15.3850447416373,8.54393556157044,45.0675365864278 +15.1456784126813,8.85950257689531,45.3930011187835 +15.1416758791705,8.89779423870479,45.683419254903 +15.0793868846653,8.86163377607758,46.4725847360733 +15.1129863751148,8.93133877782643,46.6983177900313 +15.0394633398832,8.82802498918976,46.6305210781332 +15.0072921165244,8.60915507688734,46.9214564502924 +14.9544737714076,8.45752963582419,46.6351074677581 +14.8676891574554,8.1004040477648,49.0128343062955 +14.4028787192027,7.01044697474102,47.3043074972805 +14.561970414273,7.09622155504914,49.6921333341692 +14.4870854213217,6.97329113843857,48.7752779041243 +14.3348332983107,7.11481684944755,47.1551903123659 +14.2874631405281,7.215867387082,46.9590645103526 +13.6989453815796,7.85735659517908,49.7922112049826 +13.8654393447285,7.2699562902507,-46.8183228018507 +13.7479241738977,7.36782215964642,-47.5424863355011 +13.6293797923455,7.53321979225435,-47.9328483867954 +13.6480078395498,6.36673954491388,-49.0722913292181 +13.7060900645954,6.48722011431678,-48.9432987138328 +13.6280013814949,7.20177823793779,-49.8160754019011 +13.5655282193859,7.36935072643142,-49.1457731575472 +13.5989724371025,7.31207054843867,-49.3798352603554 +13.5181191468114,7.44268356074134,-49.5600171907737 +13.5075637852442,7.52797226338573,-49.8719671039649 +13.5225055800264,6.88810085073038,-49.7103074429754 +13.5314564807472,6.93391805881357,-49.4227356205795 +13.5386124603002,6.90406554115897,-49.1549429089059 +13.5187227608413,6.9311166600204,-46.8435577768743 +13.5214112000872,7.09630703560764,-46.0327119778041 +13.6296184688419,7.40759537639829,-45.101350418627 +13.5483554217206,7.1250056695422,-45.09532823164 +13.6952014997996,7.51966225557909,-45.9387964226676 +13.5802253918038,7.52455289233533,-48.3675961125074 +13.6367987385586,7.57244733277463,-47.5702406320829 +13.441049517755,7.44895503175863,-49.5116337700394 +13.5193286496451,7.52237316962606,-48.738160775441 +12.9627321851943,6.84523497931308,-49.2407344838959 +12.847568212234,6.74774883216123,-48.9667033062512 +12.7044316392772,6.96025296455415,-48.1637789522155 +12.5882856342073,7.08040491026928,-47.4085603343247 +12.4693721397158,7.21004474426118,-49.0120277465791 +12.2584254398724,7.24655653760971,-49.6864296448096 +12.3898795046181,7.26764183270387,-49.3009326324702 +11.9538732755549,6.921959858537,-49.3574738247703 +11.9510006870642,7.03458106943864,-49.7695053624856 +11.9511767733913,6.95152781715587,-48.8009594303651 +11.961007579038,6.87668861106913,-47.5518985377571 +11.9716042149455,6.87307639268973,-47.3196262510945 +11.992082766513,6.87140969740335,-47.7409767158153 +11.9827788039156,6.74238199253243,-48.2556345475599 +12.0049672485191,6.91093658728864,-49.8368394648254 +13.2245666711028,6.77744347678659,-49.744171049169 +13.1632302366358,6.67380571045479,-48.5936701643936 +13.1960078845426,6.74885665611651,-49.1742753711129 +13.1056706915054,6.91034022094616,-47.4887250488168 +13.0634654551436,7.0457270613255,-47.2455634805168 +13.0265042752167,7.10545246404683,-46.8544043895403 +13.0020259192744,7.18213591651471,-46.2037092087219 +12.9947190963552,7.34077529324432,-46.2047129693863 +12.9116143757669,6.8692910758867,-49.8430775612113 +12.8023834202825,6.78852093475257,-49.6371919223414 +12.7523169048155,6.68749697936127,-49.2547324576499 +12.7897122181353,6.65124328998181,-49.8056294799128 +12.697792966829,6.69528154571691,-49.3025541082324 +12.5887092321475,6.80549169840125,-48.6531934391951 +12.6363200018683,6.76486186002748,-48.939393280176 +12.6056400292243,7.09362960078144,-47.0968749108889 +12.6446947998505,7.16908421532253,-46.9793559422637 +12.7665079804175,7.22449120027522,-46.0979165061942 +12.5880491675225,7.05023266476371,-48.0524958335461 +12.7137687937226,7.2801399193769,-46.2816179650522 +12.8373840410921,6.5480430306573,-45.2148968507158 +12.7801616054072,6.72434017690766,-45.0898260114391 +13.0666181365066,6.66847396328835,-45.7372535166497 +13.0746579813437,6.76560895151079,-45.2604375672145 +13.507340201076,6.86086874819646,-47.5054467298043 +13.5737813189065,7.01737092373534,-46.3240311202159 +13.4285544642711,6.74116913066556,-45.689421914508 +13.6035172951043,7.18466537868012,-47.9692852193594 +13.6168049373862,7.33090711716151,-49.6531400933735 +13.6267045545706,7.33385190058407,-47.3701853168912 +13.6200358849243,7.29551011781252,-49.97671164716 +13.6673042604887,7.26021133721675,-46.5963251094859 +13.7192255183877,7.27368359653293,-46.7111501210745 +14.1541825478947,7.50598158530145,-45.2325356099421 +14.177865179574,7.62259337395658,-46.3076230041003 +14.1464264223486,7.42886687054577,-45.503807729611 +14.114584011295,7.24500844849886,-45.2171994635079 +14.0747477430526,6.9079975745986,-45.4082979777695 +14.0047374454853,6.3266055539162,-48.7190495087929 +14.0168871758112,6.33178004392784,-49.5857596033148 +14.0032378810005,6.82388629348792,-49.5737055336083 +14.0141451256102,7.18129338945674,-48.7431984088119 +13.9828871891512,7.19309821460724,-47.233545267714 +13.9866475103231,7.14010698289785,-46.0070938539757 +13.9924470705486,7.29443854897376,-48.7940788043965 +13.9826715002344,6.90533638136347,-46.1893118594297 +13.9943537949624,7.06149569297401,-45.913957728929 +13.9471877635377,7.02358365048334,-45.1311694589958 +14.0402087958688,7.58705139091941,-48.2173253046018 +13.9583935966164,7.20660415389648,-47.0671057364891 +14.0990120738357,7.67635849161072,-47.3234835948024 +13.9692603416543,7.39941485880148,-48.0159801825394 +14.1601324066551,7.64704808543128,-46.0045302042014 +14.5271889564329,6.93713267706407,-46.9339939373515 +14.4233781311166,6.87743582223267,-49.2297114122312 +14.35164462702,6.84878757659374,-49.4041798896812 +14.1882125456803,7.04746197890988,-47.9117913134149 +14.2567508296641,7.01887614257059,-48.9429505852974 +13.9852811061917,7.38087020027584,-45.2137374598551 +14.0978204107448,7.23837317918282,-46.4785205442838 +13.2435668565707,7.12577604890377,-46.004771365029 +13.2136990129301,7.15793540163024,-46.2300255163811 +13.0476022589339,7.05102972356237,-49.1423134745449 +13.0182460489844,7.13720312900963,-47.7655949970296 +13.0123402136909,7.24022016505188,-47.4521155203493 +13.0749153865685,7.53093385111603,-48.4465163073664 +13.0097538299411,7.46531985493076,-46.8767997051588 +13.0161251793979,7.48033065633022,-46.4929856829291 +13.1383043727548,7.54142594307826,-48.635468190343 +13.2031531397998,7.4482560923638,-47.604634585841 +13.2756644706909,7.41978726024022,-47.9993471634259 +13.3710703134202,7.07669264224065,-47.2523324375997 +13.4454767564612,7.04201979728395,-47.2409703575321 +13.4526912326745,6.88543970318689,-47.0542551612081 +13.3134815782893,7.11309403429589,-47.7912413962157 +13.4621874055316,6.91595244269478,-49.7783968386558 +13.4362684901976,7.09153554815971,-49.8677580028313 +13.1885620434805,6.78415012910262,-46.0736025629681 +13.279357012408,6.96465983622911,-47.6694158987448 +13.3709622221985,6.9385062123761,-49.1065626525472 +12.7970914651826,7.28678015587713,-47.4021796934858 +12.6637095145069,7.31095313634385,-45.8181806372112 +12.7148816086338,7.35612208416369,-46.2959348773159 +12.6102558075471,7.28962302275103,-46.2297982517715 +12.556891306482,7.35541137454493,-45.2188854807628 +12.454530702395,7.49723675984141,-47.9300697385172 +12.5039632609676,7.54111051302609,-48.0504636498734 +12.4123858414538,7.59901042842858,-48.364657572773 +12.3799663007724,7.46300047581238,-48.0465137438785 +12.4227259139551,7.52703838478476,-46.481422152188 +12.4816993129862,7.29796050217749,-47.7452095356745 +12.522748872886,7.1179593199622,-47.2751155564761 +12.5102023549287,7.15031911353045,-47.6397598922675 +12.554772825868,6.95168705577629,-46.5187241679384 +12.6213280039512,6.90085531244309,-45.4779427390721 +12.718684017401,6.95298645275491,-48.1948689894994 +12.8487771184769,6.9960737323336,-48.864590694901 +12.9110686887353,7.00880377174243,-48.948313734407 +12.9447713706467,7.06579882488142,-48.0041689882719 +12.9783590125775,7.03259746405873,-47.4048526858501 +13.0119956846857,6.81295523272318,-46.6183141574735 +13.042140980392,6.78898915269307,-47.5393920008932 +13.1024778585334,6.72224616673561,-47.2028206612464 +13.1705734372361,6.6585945481623,-48.1746033299742 +13.2205453935395,6.71751757865184,-48.4756278768834 +13.1626330175658,6.59805338472801,-45.292416286105 +13.1776328126281,6.6116491935475,-45.077436823182 +13.1979401072089,6.62262205774984,-45.6502229956785 +13.1859316135656,6.84602807263967,-46.5515802632962 +13.1470285590639,6.77690378348148,-46.0604585857707 +13.0754482630627,6.77941580288088,-46.4046638737078 +13.050347000451,6.61840494385922,-45.3184806118634 +13.073071675431,7.11851803963791,-45.0757854425535 +13.1450006290287,7.70245080807772,-45.5312312177134 +13.088273141094,7.37510199348974,-45.5510718856806 +13.1686154693356,7.81021613751451,-45.609135167253 +13.1306634732187,7.58589806860723,-46.2937721989731 +13.2657965025884,7.46783283326352,-45.8657703125194 +13.1995845641512,7.56852609639783,-46.1723578003318 +13.3412562330709,7.47330347750457,-45.9263205893195 +13.4154576537688,7.41814055216932,-46.4198028039804 +13.4055188335288,7.49903792109908,-45.7767121277494 +13.492057432467,6.97468490395388,-47.015815253329 +13.4637928878391,7.05885816210244,-47.4407321668593 +13.518443611918,7.04612264723616,-45.5811946218734 +13.4257905357831,7.03814740509094,-48.5575847662481 +13.4080918131369,7.02001525043709,-48.7979979915696 +13.5463313029149,6.81242182756326,-49.7467384426303 +13.4644510721824,6.88838871868069,-49.673697499635 +13.2264837050595,7.02575545627813,-49.4534923245202 +13.5344801436078,6.65801686426783,-48.9437731697622 +13.623322337915,6.67631160799362,-49.5031170842493 +13.5553376424116,6.76126599207022,-46.4986808357107 +13.5345817800703,6.72317672307823,-45.0553219345046 +13.6917038595643,6.72280569069932,-48.5484018360073 +15.2199167259523,6.58763690898471,-48.9102764431561 +15.2858863535836,6.48085896878592,-47.862559871046 +15.2393290567092,7.06857106054021,-45.7229946805228 +15.2154280329145,7.19283097372488,-45.4789206709944 +15.1715260151668,7.10048381314016,-46.6226933596374 +15.2124518869415,7.16771093924779,-46.0060187857549 +16.289411318833,7.73824828453008,-49.8481378405923 +16.4849731304614,7.72483916821393,-48.9861489570606 +16.6444112575028,7.4612702243286,-47.200625532811 +16.7457687020885,7.41322039804365,-46.9646565753057 +16.7982314992851,7.3457112294604,-47.2174868253379 +16.7948128061375,7.42658884781029,-47.10198153748 +16.7836694011978,7.49752148054763,-47.4799855708832 +16.7680087297662,7.7167792241858,-48.5065429143185 +16.8662068596861,7.87402670407094,-48.955447055696 +17.0082957434198,7.90815164235203,-49.3853642744993 +17.1061695624588,7.98574873140119,-49.9671358217117 +17.1748107373874,8.02094042137001,-49.7734019175157 +16.9751217879748,6.5188454782126,-48.5677569390657 +17.0449948507573,6.69463213898369,-48.6123256303262 +17.3763970502342,7.66531531635388,-45.9830204983846 +17.432603540433,7.48268905287336,-47.1976348021576 +17.3180103087155,7.73105519002745,-45.4694137492636 +17.4116229187999,7.59637099715034,-46.180808910863 +17.2167362947233,7.63142275507162,-45.6474908311051 +17.0400160828526,7.68803611649679,-48.4723839404247 +16.9434241106977,7.67206728945493,-49.1981830985096 +17.1458685477373,7.62792157388032,-48.0071651672508 +16.8463643714006,7.72177049428435,-48.5689687693814 +16.7051294289593,7.71414708228437,-48.3579165546317 +14.9804206263743,6.84808547014656,-47.5886321116462 +14.9650644096172,6.66876857178911,-48.8745059955467 +15.0028601387054,6.48017832217348,-49.2579672633651 +15.0083382246343,7.29389589350917,-46.2041660968196 +14.9918316583455,7.17699498595785,-47.0464194409195 +15.2627932224506,7.10352593296746,-47.5375845515786 +15.0324757970631,7.1832331619187,-46.7174143739767 +15.1471611741271,7.16262125573764,-47.5430340713367 +15.3475130540169,7.18357537989509,-47.831756456359 +15.3923464086658,7.24466877683796,-48.7435635461678 +15.408706824017,7.47836505249067,-49.6904940298704 +16.0841463233505,7.14076630917315,-48.3458290137292 +16.4529509862433,6.69490024208447,-48.7828601134566 +16.3759767393764,6.88814547074631,-48.7703035760573 +16.5581739377234,6.65040146063645,-49.3959470527858 +16.2449636985946,6.98920457578942,-49.7891380929961 +17.028135279285,7.1979078702484,-49.6983385831942 +15.7654193808343,7.65068862486853,46.9436281630093 +15.8134656021336,7.53086863349397,48.7253592812148 +15.794609859455,7.57926683098646,47.4948490971957 +15.8487719503123,7.51968304884251,47.3479373794857 +15.7875844757294,7.73731060618328,45.8830071209779 +15.848209602579,7.49051420265515,46.9575181106055 +15.8606458284324,7.57184085609615,46.411812335823 +15.8234911860393,7.61793962131447,46.1991592543591 +14.9150488258455,8.35268940722463,45.1238079308298 +14.7406485075584,8.17988467021798,46.0289631541762 +14.8566323224254,8.20813853806118,45.8321161504597 +14.6564624732554,8.22059561767503,46.0522848330036 +15.7187959471256,8.07041618359471,46.9285900877383 +15.6328433807651,8.07545253637161,46.9494642335783 +15.7153422070554,8.07403684284688,46.2617677450036 +15.6986215613714,8.07487562590248,47.0361498707377 +15.7956977120433,8.19086136552177,45.8038578537538 +15.6946762196992,8.06963974469962,46.3013804906336 +15.8498866765761,8.28570334072257,46.2351611215446 +15.8502544488266,8.31150035257978,45.8766381627551 +15.7330097787157,8.22867543910503,46.7794275688766 +15.7822735809386,8.30723378491913,47.2755553310436 +15.8220945687365,8.34146140593775,47.1338933767569 +15.6679664840136,8.01944941302144,45.6586006372567 +14.6272705600327,7.5715112454634,47.8526095457071 +14.6016004060576,7.61309903094755,47.9652625756203 +14.5876418031444,7.6719792450305,45.8585695732641 +14.5990316854932,7.62928185264908,48.779739581698 +14.6172870927862,7.55809577143767,48.9307849757608 +14.6590791478023,7.35044677155959,49.1592473842882 +14.6203334717562,7.42993055722172,48.2966703342587 +14.6618267678755,7.28523532456509,49.5930345144331 +14.8215162057616,5.91526167931964,46.8511553671964 +13.5389309612272,6.81627923187655,-48.1961840589736 +13.5693315517078,6.74495394479447,-49.0220637419274 +13.518149531295,6.80388794848909,-46.9018065109523 +13.5072882567955,6.80218563297241,-45.9804851441675 +13.648030189117,6.71176074537224,-48.8533818446442 +12.7602131220817,7.43964909878157,-45.9165160741023 +12.6639477290028,7.59151070793876,-48.1340063029421 +12.6901319401189,7.52717489474331,-47.9455922695219 +12.6420786986126,7.67748859426301,-48.0963768587307 +12.5951340951274,7.63794308055262,-47.9689159717265 +12.5652200939924,7.07513482567504,-45.6822018117831 +12.528637918322,6.74731380820461,-46.0318197460979 +12.5170002886615,6.54284235014806,-45.7907648553442 +12.5249822790776,6.36200549131662,-45.7483387113669 +12.58131855009,6.32206077469042,-45.4106657596312 +12.670798181663,6.54271184641294,-48.6930171771956 +12.7667438599411,7.05346972069401,45.3002041859254 +12.7206512425362,7.75083426936586,48.6308204666803 +12.7164764854882,7.63119986263476,49.1201667917241 +12.9505225323584,7.54311952460527,49.5539899282658 +12.8052289686535,7.59570779912235,47.7600933091701 +12.8639643449766,7.5568716112035,49.256210259416 +13.0703606245904,7.55561597336079,49.5737606819698 +13.2300590308022,7.81520443284291,49.524957815213 +13.2628714132732,7.89044809601186,49.6531326133157 +13.2292188345501,7.76301927641309,49.6045031690221 +13.3121404128123,7.89425692078553,49.9177245249901 +13.3528148700715,7.67172755942626,48.2231014801559 +13.365879174232,7.57985371148501,46.8770834386094 +13.4208174741614,7.60153332065522,46.2785355435862 +13.3579530498787,7.69414696957678,48.9345253573323 +13.8371543021166,7.97546961798797,45.3879002320824 +13.5314194247422,8.58690442442809,46.1061731130529 +13.5404702499743,8.54041917542957,47.3492473612118 +13.571445917605,8.48469757675308,47.8642876010672 +13.634364702428,8.46743205381624,47.7349784508048 +13.786702457989,8.31524251442644,47.5557442012436 +13.7282178682302,8.40526964040271,48.3029594322012 +13.8177505682066,8.5182451233659,46.3899114130381 +13.8423972765516,8.56406932501867,45.832556750818 +13.8179576130901,8.62844328856771,45.0351307217506 +13.7684824900026,8.66002524755996,45.2225471293966 +13.7201656213524,8.62814895000856,45.2672131975625 +13.6900243666952,8.35003882528506,45.7422797731133 +13.4958635825719,8.63717256205202,45.1865994552153 +13.4942963294996,8.5308571807982,46.3034021375962 +13.5061167240263,8.43127613542862,46.6319664269624 +13.535251224772,8.08094533413698,46.6528751976681 +13.5080094860227,8.23799156582861,45.9217705803881 +13.4841366774618,8.35680845293888,46.1596207000798 +13.4216308523461,8.22757204445374,45.6291328676525 +13.4449305222463,8.30699644950272,45.870696439499 +13.4189577379442,8.02168016520805,45.4642902350013 +13.3943422578803,7.99507267166646,45.7923545258331 +13.342653561607,7.87569364355872,45.003274050704 +13.2273297045767,7.8800758903802,45.0965524613237 +13.206324631033,7.91198308971157,46.0647198900596 +13.1761074384742,7.98605141472438,47.73215568509 +13.1406948147769,8.11151617599506,48.9659083206909 +13.2239157630014,8.04227016891164,49.6722089837893 +13.1097325538862,7.54181355760118,49.335533728266 +13.1598450460397,7.58213203423168,49.7756787193556 +13.0943346840712,7.11577776289015,48.7973737985139 +13.0881927703501,7.05161151376681,47.7969299384482 +13.1036332063895,7.10464654474407,48.5492127435926 +13.1493680076954,7.1279345149669,48.4886704354493 +13.1475959645599,7.26784487293638,47.7030447744979 +13.4712866520625,7.30760578528996,-47.5048470329718 +13.4078246303106,7.11954067571666,-49.6495951533503 +13.2823157629772,7.05439013207242,-49.4035420290229 +13.2111672120563,7.12453071592397,-49.4431948744261 +13.2454420034696,7.1122050317142,-49.4398672938193 +13.1483621817753,7.07140453194917,-48.7176086749079 +13.0745348583665,7.04064333906996,-48.9585758036457 +12.8458337845823,7.09600274956465,-49.0726357156093 +12.9880055897594,6.999896495973,-48.8443757771483 +12.9581137672136,7.05236171210038,-48.7191604096697 +12.9202119648842,7.04277581215751,-48.712612734039 +13.2741325744229,7.0535617360664,-48.8657187429997 +13.3286500077,7.023562584197,-46.8557311629185 +13.272790840457,7.16149380305746,-49.9329355594442 +13.2782584873532,7.12949601368049,-47.9372289927487 +13.2898276901363,7.08848943746323,-47.7256109512707 +13.5076088014533,6.8459424081236,-49.8339064284439 +13.4647846343757,6.85758248399421,-49.4865693665458 +13.3997157338506,6.89578928408653,-49.3228659022672 +13.5645767997804,6.83028494081125,-49.657475650375 +13.5855378314819,6.91930951612331,-49.0328525000443 +13.6035553251316,6.89432708704056,-45.3183505539162 +14.1245419087298,7.21033170298148,-45.6756448569757 +14.5694729153201,7.21474260118734,-45.1407749073412 +14.5796811454411,6.98750135061408,-45.7923597522185 +14.5746945380992,6.86021786843393,-45.8850126844512 +14.5870744043378,6.96728644605323,-46.9975680881444 +14.475837808194,6.94238993687099,-49.8193384344739 +14.5229385080949,6.95799030373212,-48.6597922902766 +14.1395543857841,6.79920573039616,-49.6920093322933 +14.0779389036511,6.79652235231675,-49.5324676093556 +14.0638022784451,6.77072907451885,-49.3371164589797 +13.9812728059413,6.61215508682264,-48.6127376258114 +13.8993070179874,6.81586745051683,-47.6801609117763 +13.9387332986824,6.77670228921841,-47.7967621642819 +13.8655033951731,7.10438582511754,-47.129648773146 +13.8855606472287,6.95239177501762,-48.115695821997 +13.7947555176845,7.17907046435439,-45.259163855145 +13.8248924853716,7.16699740957022,-46.9285348987486 +13.8714567578543,7.16544720801521,-45.2394533297863 +14.7509095263541,6.82344111268535,-45.425707136227 +14.7361631369437,6.79589102518371,-45.8556440593194 +14.4228345403213,7.4094583076725,-45.2365937183849 +14.407674222609,7.36779224277449,-46.0241002978677 +14.3683026801435,7.31937485110401,-48.0746270812232 +14.3470236079568,7.29687845423708,-48.7321138394238 +14.2540725897207,7.84564962737658,-49.7087071246561 +14.2912624298975,7.69165727723212,-48.1233418870723 +14.3453821816124,7.58102739934611,-48.9449834362158 +14.3402249792101,7.71573989902609,-49.085531595473 +14.2231049606302,8.16147659080729,-49.5464576038335 +14.0295230073694,8.83958264434866,-49.9193277948164 +14.1104622491015,8.70633199245927,-49.833649243696 +14.0730222362799,8.7316279264273,-49.5792264666942 +14.0047251826924,8.97175650269787,-49.3436970374695 +13.9630451200187,9.03314312375327,-49.3198824458158 +13.5942361511614,8.20405326578591,-49.0263694845706 +13.6120762851111,8.25015752120804,-48.4588772495539 +13.584352088037,8.23806624649058,-48.516329581321 +13.6808271543284,8.36087414898756,-48.5275040579756 +13.701579910988,8.46969968860561,-49.1226241351502 +13.6999405129552,8.97800373071534,-48.3952485589813 +13.6464638567822,9.06443226337032,-48.0677732414132 +13.575499385604,9.14880988796859,-48.2483126574769 +13.5081082229284,9.2261043854262,-47.3924811390071 +13.4139193762437,9.15292975950751,-47.108322340861 +13.3450572738085,8.88675190994346,-47.8005781757105 +13.3173693906755,8.61780060507114,-48.4013906563053 +13.3113645776846,8.7308244002876,-48.362453058706 +13.3200589650217,8.42223460035066,-48.2473682614123 +13.303441385021,8.27064628339834,-48.4033028343758 +13.2776082708889,8.55115958946191,-47.1560856022769 +13.2470552952564,8.78229179670852,-46.5804723205187 +13.2831147900098,8.72796696040153,-46.8106192894882 +13.2015623291294,8.87280562266251,-46.3992378792157 +13.1922362094761,8.89427464914849,-46.057288564226 +13.1848602290241,8.53589144121243,-45.5692221864209 +13.2003064527202,8.43477552192133,-46.577827862137 +13.261695619414,8.35089422606759,-46.9214834035399 +13.4057119801672,8.4301732532623,-48.6902725087908 +13.3347593760464,8.31139486587136,-47.3394994559027 +13.7089438296599,7.98486509685881,-49.5351205756729 +13.4728657046123,8.06419134654796,-49.5526707761847 +13.4123632813643,8.1062911201985,-49.7341264525469 +12.7178304944093,8.18144702081816,-47.1773407653585 +12.5958031567723,8.11325486242144,-45.013987942956 +12.4932231394175,8.0107943071475,-48.2862417868494 +12.3853431178222,8.36135475387456,-47.0554762052981 +12.3672763361211,8.42701968327625,-47.1730192894118 +12.3436303607565,8.44847009352241,-46.3021730548376 +12.2923988798808,8.50739677439526,-46.1647293146668 +12.2331856328225,8.41605093805093,-46.159552863621 +12.1860900329318,8.31757256852188,-47.4531939602884 +12.0962272598814,8.26789983696738,-48.5491596181664 +11.9934963283707,8.29002684330483,-49.1428019987361 +12.1463590776473,8.30934892839684,-48.4404490750192 +12.040171241421,8.32203723306064,-48.7575166055253 +12.5795900471083,8.27516490673442,-48.956152960025 +12.7012503590509,8.55214436445607,-47.8873811715154 +12.6326118835087,8.36427285398684,-47.8584965918829 +12.6744105846328,8.50397262005159,-47.732620381904 +12.7663062061346,8.40798440473908,-48.3192263163247 +12.8526525090105,8.17930452237865,-48.8507948252709 +12.7066291691201,8.51072039404204,-47.9745130375078 +12.9402165632941,8.02905853349105,-49.0357870485995 +13.1487263097084,8.03710148233021,-46.2496360523964 +13.0400123591578,7.97840798897467,-47.5400037891786 +13.2676010580244,8.19059058733816,-45.7050760555661 +13.3537951194953,8.41705571410397,-46.062377207333 +13.4360904981619,8.67363870840514,-49.5330644706991 +13.4089172533579,8.57661270281134,-47.2364119012849 +13.4555351186814,8.08765384451602,-47.7988366610305 +13.453722911947,7.82402444252277,-49.0309226239626 +13.2048107704325,8.31939657018541,-45.2902570710233 +13.1537221278631,8.25487243493143,-45.8555926157253 +13.109602953903,8.33222358542999,-47.897315064137 +13.0932080733602,8.40647857895181,-47.4646835839008 +13.1083394541123,8.43174158375544,-47.7819288632742 +13.044019461976,8.37718553149977,-49.8696762034784 +13.0615831772709,8.96124042032916,-49.1133327116329 +13.122358641338,8.7680874441805,-48.2532481983876 +13.1242974398623,8.70399401107234,-48.3417132246562 +13.1285517912243,8.61303043068281,-48.4453516431008 +13.1378152364392,8.94114077750874,-48.5987826476315 +13.1153530078056,9.00501054748223,-48.7605809586277 +13.108149195281,8.39615026900257,-48.186713776634 +13.1115688803233,8.22480429300916,-47.3969118576667 +13.1521286521078,8.03639395661028,-46.8686636251153 +13.1889340070599,7.92915076246638,-47.2195115411771 +13.2137873667293,7.96740233745787,-46.5664302855239 +13.2503779100796,8.17606934761022,-48.0354372633711 +13.2907323049301,8.21818763121155,-48.6463606409425 +13.371097383612,8.25025819537782,-49.4142634795551 +13.4733380421712,8.30935142821569,-49.4480863969841 +13.5920034940228,8.23954252382059,-49.5947693480845 +13.9189445317401,8.32967136276029,-45.8402491135377 +13.7946842945917,8.3889843844872,-45.591194144856 +14.0357782940644,8.40475442942828,-45.7158212320639 +14.1280501329482,8.4050918661662,-45.2904803983297 +14.2233011635593,8.33759131954929,-46.0909774601779 +16.2270450133236,8.36542640687926,-46.2490347360896 +16.0263457487572,8.33454124913614,-46.2403655544539 +17.7401734857882,9.34896851767549,-45.5509795048557 +17.9069870643393,9.93576819224852,-47.5928147458331 +17.8268784949667,9.72600149992155,-46.5930785117009 +17.9801222334653,9.97280776639236,-48.0340256770167 +18.0915969325515,10.0418424423492,-48.760128692479 +18.2459667198119,10.2612123259597,-49.3782098285967 +18.4569838079084,10.7002266526707,-49.3658226337064 +18.4217337714747,10.7763540914725,-49.518508217959 +18.356825464588,10.5755948977796,-49.5312154466169 +17.9254554881667,10.0060275188019,-49.6634571810862 +17.7845094126968,10.3171523821669,-47.8562351823627 +17.6903337547543,10.5546171693446,-48.9656396749051 +17.5859705657568,10.6233431326177,-49.5989254403403 +17.8381734245508,10.1451167577492,-47.9754787495436 +16.6653863915391,10.2297185685964,-48.2812961116577 +16.7721663385305,10.3180775717201,-49.1202070529908 +16.5576528403192,10.1667113582971,-47.7347459621251 +16.3825572871189,10.2550439434403,-46.264542515116 +16.2610229510805,10.1773984828114,-47.9790120781593 +14.5685111877596,9.63643859389644,-49.6763586373929 +14.5283646608182,9.55797144287351,-49.7850335241665 +14.4626855533955,9.59372750317575,-49.6306728544661 +14.3341353781018,9.7271259653006,-49.5149286453813 +14.4090323047362,9.75196481735702,-49.3610010959034 +14.233154140923,9.83599806381622,-48.4299507145377 +14.2807012479911,9.78458375929391,-48.9097871989443 +13.989154322204,7.78194306332007,-46.1914566719846 +13.7569186052986,7.57598958508072,-47.4164403677688 +14.9507199755902,7.4531837513804,-45.8880460347661 +14.9486582670115,7.43236526826786,-46.9864627611686 +14.9273997031245,7.40959420042031,-47.0106844808849 +14.8758055037603,7.43556274873168,-47.021874910157 +14.8285409396123,7.40039601690909,-46.830022939691 +14.8179510715706,7.30464687421168,-45.7078487439456 +14.933151420978,7.41161054044012,-46.1162046465424 +14.8521995285657,7.43785875615059,-46.763095205544 +14.1365293214623,6.79305660270076,-46.0294068889455 +14.001307602549,7.67714282317485,-45.0634449415398 +15.036638915997,7.53527089551789,-45.7084818905208 +15.1106819153139,7.40958166767024,-46.072415521314 +15.3063597323057,7.28507567006078,-45.330901441949 +15.1482028805309,7.36231784431108,-47.5963409324014 +15.2210668972582,7.21515655231703,-47.135904037978 +15.0462352558338,7.24623031319794,-46.36487003666 +15.0871061346363,7.32661570047533,-46.9173329003055 +15.0986503638987,7.20449034433236,-47.1680757309171 +15.0610891419139,7.22909077301482,-47.3417958773143 +14.0804244347751,7.55199881149498,-49.4660317709271 +14.0444832247476,7.74682840708042,-49.9962223366322 +14.0999149560323,7.41705543411667,-49.8947070386727 +14.1020490907474,7.27614923528032,-49.1164528098377 +13.8808071054457,7.7821494602881,-48.5759620324955 +14.0218021475083,7.77991666390221,-48.4206361382161 +13.7367928053082,7.59121402704663,-48.2753718024552 +13.4583208652001,7.20614723634916,-45.2940456555589 +14.6914689203247,7.41603550129895,-45.3526756031183 +13.3669483052888,7.04728120435468,-45.3442064462853 +13.2776211208514,7.07299102880503,-45.3331434714975 +13.2101051506798,7.08913025437785,-46.0322542523962 +13.1930931898071,7.23857298769776,-46.038269860617 +13.2298852646841,7.02729257450719,-46.0227474677239 +13.1935173301278,7.29727006290242,-46.8759703001637 +13.189202364161,7.39901051683985,-46.9384888026723 +13.2599585168435,7.68461669941006,-45.229646602703 +13.2314085374641,7.56350230160343,-46.5965877320265 +13.2627745448076,8.08923677617967,-45.0265836363445 +13.2475458269377,8.32287046659105,-45.0691036844603 +13.3016604659669,8.63604530303235,-46.0256374571637 +13.3775336501997,8.62177824157155,-47.5548127273155 +13.4949273847867,8.53371101997822,-49.1174931395078 +14.7342320421506,7.39859287767579,-46.0053444179646 +14.7787419965196,7.55857637400738,-46.9762856623865 +14.8455336643925,7.66792158039619,-47.5354190047713 +14.9020285297995,7.57127738657614,-46.5209459818777 +14.9446235095596,7.38697702679671,-45.1460536379863 +15.0750895371271,7.19532149077297,-47.176183989432 +15.0881905968877,7.17805056846971,-48.4574721415402 +15.0891353604929,7.09675634289623,-45.4804163513932 +15.0889726094092,7.29314799704779,-49.0349070290524 +15.0844160053402,7.21385451773578,-48.6485039424807 +15.209973235559,7.93979212774932,-45.4916893359353 +15.1208124676942,7.75865823346702,-48.3866710133398 +15.0909807560124,7.50481332410083,-49.0084230855618 +15.1629923788735,7.89534449255885,-47.706960818233 +15.2006799174381,6.97611338881066,-47.1032589568183 +15.2061225904442,7.15846073105989,-49.4562111011456 +15.1996014322633,7.09456908977534,-48.5382165854456 +15.2149200290362,7.22547258826531,-49.8700229439809 +15.2894990676684,6.87314302068229,-49.6273160199317 +15.2845025226738,6.84017597692211,-47.6401326800114 +15.2230887741096,7.00850825895042,-47.0593207869938 +15.1449048585095,7.02995177139602,-46.7739603546116 +14.9874427837408,7.2390056676359,-45.6074578267736 +14.9499554469616,7.39056204343347,-45.7030943862707 +14.9218607906534,7.36999917153958,-45.6821927921536 +15.0479111586822,7.14092468010813,-45.4828513200987 +11.7293263628628,7.18050460944474,49.4916318992581 +11.8796892548967,7.13050837016822,48.9832940389661 +15.0144731006357,7.28516549018673,48.0565789548345 +15.1901663967839,7.54514772774603,49.7450218973476 +15.0655040448932,7.46560958203084,49.4242279884173 +15.268469980742,7.23116300256844,48.9164199621772 +15.1246831553206,7.57635394131072,49.8526595522677 +11.6024991614411,7.21812678793738,47.2574324237921 +11.3676441069819,7.24771763475252,47.621386471123 +11.4983320917998,7.24024050969744,47.5287064947782 +11.2676098204112,7.27513527966355,46.5857431012948 +15.2543595116397,7.45260702647461,48.9322385903004 +11.1699252284706,7.19873859927652,46.790715755025 +11.0121251617371,7.25377221962661,45.7201718440494 +11.0887638607337,7.16570159904045,45.5201585851322 +10.5649210071255,7.80685355686199,46.2149570981648 +10.654260031474,7.69598369017351,46.4779899718892 +15.2744324619879,6.88317959914481,48.2890691744916 +15.2770850261741,7.09113395674962,48.8732227303067 +15.2742962892493,6.67720563254344,49.6044982228463 +10.8252334841205,7.54288186418449,46.8972795155747 +10.9373733581499,7.53637519799442,46.9049336865333 +11.0406169472861,7.55433621841777,47.5711913254671 +11.131706218462,7.56761517144064,49.5862490150218 +10.7407474516915,7.62416232696893,47.0950499098075 +11.2513750119006,7.68550426187443,49.1190108235717 +11.8381723194882,7.9031781760274,49.4174211443766 +13.185763564292,7.90165179281363,47.6446595464144 +13.4151323975402,7.89342890342564,45.7420338811566 +13.0391044415877,7.72657167346789,45.677127912171 +12.8503267073107,7.67373670564313,46.3782344972748 +12.8569757236101,7.67491129230325,46.4261816607594 +12.7588771338913,7.64599244739323,49.8175889300887 +12.8734400846052,7.6472729971443,45.3308471544398 +12.794457652028,7.61046493339248,49.109186373769 +12.7959727881252,7.64877293629666,48.7941221309829 +12.7844246690031,7.55402802288905,47.007190747608 +12.7650690681741,7.54032416678457,45.1259476124164 +15.440135333565,6.97588543239796,49.8268039444461 +15.7657981504991,8.01399367969209,49.6440933051523 +15.8307085751237,7.99751403911179,49.4260386164887 +15.8868104339203,8.14732875134633,48.842181758303 +15.9419596361787,8.37593979955491,49.6243192525416 +12.4441620808057,7.20540090855161,45.6987458708027 +12.3623074775114,7.12223851367798,46.7206957479805 +12.2444497715636,6.95528471549751,48.6339917998704 +12.2159469167315,6.8434019615915,49.6196052342181 +12.2964053239212,7.03249801628882,47.4768819056457 +12.1032418104771,6.64789080570225,48.181138385502 +12.0913534023213,6.74334884126594,47.6110694251731 +12.1240475240403,6.80910529264545,46.8003398898002 +12.1380115527687,6.71738478835716,49.7707848754447 +12.1047712399993,6.84197406510845,46.7107947812128 +12.0529831583556,6.94523567619145,46.3832732027194 +12.0293704261126,7.20416885898653,48.3327464124234 +12.0439741390775,7.22541896579207,47.9492435487001 +12.0333065493788,7.0647977036873,46.713438929742 +12.1291322385317,7.21653858938073,48.8028686363685 +12.1444653373132,7.14872295084329,48.6191315334434 +12.1650272929396,7.05492959423884,49.890079477838 +12.0840781815475,7.26139690711336,48.5068828846104 +12.2008061886587,6.75826237182894,48.3542587120602 +12.2012906692631,6.6938838136297,47.7260665434625 +12.1782426456145,6.65701741550359,45.8906071740663 +16.0717642311255,8.33795592638537,49.9755712353049 +16.0960959161826,7.73564400199216,48.879459606437 +16.1108539097083,7.79246611660279,48.3241811526857 +16.1206473958603,7.91050383183653,48.4337071549892 +16.1233520327075,7.77519116384746,48.2049912765325 +16.1311264302331,7.94600405681488,48.3050112452674 +16.1637469729381,7.98770378858613,48.0491013212286 +12.1717602860889,6.57011673040992,45.0608444820484 +12.2614644418039,6.62807798938537,46.37272335342 +12.3607100425131,6.8096021844182,47.2135782365706 +12.4211617175761,6.92401742715944,48.9108144516671 +12.4658961641797,6.99688464145049,49.9331986565616 +12.4858709218087,7.05136275495476,49.5848485814768 +12.4810236125549,7.07080515610518,49.5052463978727 +12.4832838622046,7.02500907437429,49.7005720726018 +12.3681880187472,6.019563956945,49.8449324850758 +12.364124547101,5.95469711954971,49.9494909431934 +12.749746873632,6.57129890075874,49.3141414631469 +12.7975178528724,6.4953672341714,47.6569641908039 +12.8261703338482,6.43590062610302,45.9871287115393 +13.0338101781271,6.15454499727318,45.7564990882126 +13.1870407572168,5.99237455714434,49.2562408987435 +13.1138354470951,6.04893435494483,47.1197418375776 +13.396949623684,6.11580349753162,49.8083600819877 +13.3757868394562,6.14013962050246,49.8539749695993 +13.3375084418709,6.18749762279947,49.6265862781965 +13.7499308096639,6.59346590900655,49.068055252148 +13.735927759825,6.65673771789321,49.6760000873273 +13.7119074843229,6.53659552495984,49.5904613670553 +13.8440384609809,6.68763680006708,45.6208179079321 +13.023912837251,7.00192101716661,48.378907675965 +13.3124766776211,6.85972360712108,49.0781401819838 +12.9376963317138,7.15973992864784,47.6785778027347 +12.8627639988786,7.23106187603711,46.5774691014806 +12.7831185862055,7.27183511483679,46.6720073854187 +12.512972282617,7.31118930233707,48.363647771866 +12.4181241166145,7.40259388902832,48.4195889258851 +15.2288097627401,7.45209094482806,-48.5413379822302 +15.2154618632882,7.6536857691803,-49.772096664007 +12.6131418346041,7.31241809950429,47.0296836575137 +12.6952070687553,7.33125804978354,47.1466159075622 +15.1098054636915,8.31780493801985,-49.6002036177731 +12.2394755056941,7.49189895848995,47.5193007671452 +12.1966066192618,7.48406322191599,47.3850076125106 +12.2596513204195,7.44816490761708,48.1793904834464 +12.3313064942807,7.41931220092599,48.3253867793486 +12.1286971672461,7.44496965487867,47.8200310721537 +12.0310305326779,7.38768300731717,46.6531170518676 +11.5892163706557,7.15822680940837,45.5367256907397 +11.7331083119065,7.22670428477166,46.0103713922683 +11.877489328438,7.24552339323513,45.7233001481767 +11.4607825812598,7.10805917850365,45.1866927980981 +11.1379929702314,7.13313035989616,46.312278217013 +11.2290169004864,7.10053704409377,45.5580780541208 +11.0501893922127,7.14291308553897,46.8944855101927 +10.9869332872724,7.14223567137809,47.6008327938761 +10.9470174639801,7.13013633848708,49.2147609420031 +15.094224830522,8.08619888072348,-46.6066081791475 +15.0927890724332,8.15997512764157,-47.4309193610349 +15.0887583212444,7.84801482435011,-45.1023000420141 +14.7917637122124,7.39337261872271,-45.1527423623531 +14.7481582149378,7.46452991676195,-46.6214406263225 +14.7312825212838,7.40143917518236,-47.2890640204036 +14.6969126085827,7.4002321405516,-47.3106818445025 +14.6781706418547,7.41829174648934,-47.3168546440472 +14.6541678166777,7.24519163091277,-47.2848652486249 +14.6403925988828,7.18434872444047,-47.426639780007 +14.6574833558185,7.09676532781744,-45.717391728794 +14.6047960251326,7.06921169636458,-46.3466350673681 +14.6412408649178,7.10544568292062,-45.7118309114728 +14.6636043312973,7.2321304282852,-45.4852097923946 +14.7137960858216,7.04605765024533,-46.5557699535195 +14.6213246077914,7.03917431387645,-46.5560034739137 +14.9428024294196,7.12579782312446,-47.9285046314668 +15.0334469685465,7.07468086589633,-47.5642838300137 +15.0823399688509,7.11568133110061,-47.3438182000018 +14.8118925448915,7.15358968608873,-47.18587531012 +15.0731821514244,7.14676874647392,-47.8095307218497 +14.6722128010712,7.77234477785776,-45.8555221798378 +14.639373657671,7.71009737895822,-45.661159218458 +14.6956401635814,7.82660300048005,-46.2716812016915 +15.0926187795666,7.20940217212419,-47.0963432158343 +15.0370102000298,7.29459338693367,-46.5815393960164 +15.0800911371553,7.26507762989887,-47.2249305637749 +15.0291585897567,7.39932581574668,-46.7684776437836 +15.0024620202308,7.43157653133461,-46.9486377482172 +15.1361144696663,7.33837788820408,-46.467190109791 +15.0879602614795,7.34427223770478,-46.7610669814252 +15.1897579027189,7.32717002773492,-46.3848438652548 +15.0603271552008,7.35231375619735,-46.9099867327799 +15.2256954688985,7.30714526475834,-46.4759122641478 +15.2309012404808,7.3127224343858,-47.2012622335143 +15.1171691586594,7.13419413391693,-49.3480302805595 +15.2103335681739,7.19106697564663,-47.8823059277258 +14.8095257042877,7.2442696560125,-45.9866431176511 +14.7768662438358,7.01566368005315,-47.4120376027206 +14.8091438121052,6.57005712690532,-49.4737691459577 +14.6882526620837,6.49408893951011,-48.5838712163492 +14.6792771715651,6.44719015630113,-49.2584041096921 +14.759466948088,6.47794645803741,-49.7376966160821 +14.8779900241771,6.54154188386397,-48.5880336977601 +14.3090472213851,6.4728214467034,-49.9931978798942 +13.8044890540849,6.57548241484977,-48.0041315703528 +14.7822309079255,6.79235150226063,-46.7580778529501 +14.1102373255258,6.49737456477205,-49.1487586118374 +14.6805607008569,6.67970263665338,-47.5318631309248 +14.8617255276682,6.82869133632421,-46.2623756971855 +14.9458200374938,6.85018446387285,-47.6298358675723 +15.0019892033923,6.79540885877577,-49.0340530348563 +15.2428660821746,6.83931461364946,-48.2398343693528 +15.2476751356017,7.00379691095569,-47.4054298900357 +14.7361413647081,6.90683894022074,-48.0841959920172 +14.659884281566,6.93039152004735,-48.4924512225623 +14.6377273102162,7.08751900523478,-49.5195429423718 +14.633555350386,7.02923743670914,-49.5532344903737 +14.6186733712476,7.0445263679875,-49.6952981140421 +14.5797787397419,6.95561456296582,-49.2936897866192 +14.5018780320103,6.97428323148236,-49.6420692382481 +14.452935447084,6.94525776607721,-49.7092311942145 +14.7003479451848,6.78338063539471,-48.2016635043153 +14.4165262343705,7.08564776754279,-49.282529475478 +14.3774797992849,7.20224738893278,-49.9504627944201 +14.4266732318857,6.96583758014546,-49.1855381595269 +15.2172643283206,7.12613767586944,-46.7778399981451 +15.2032980841445,7.2236136588275,-47.4960290690198 +15.2227747464402,7.25679770757886,-49.0787313163821 +15.2455755546784,7.07333176604349,-46.4239032185456 +15.2302655279034,7.74599821794287,-49.6934080712286 +15.1897236627896,7.66396724516546,-49.6020158179941 +15.1760966663401,7.62109126809897,-48.8484989000595 +15.1479774823717,7.45279389347076,-48.7847511054993 +15.1320726428179,7.24422614883849,-49.6708738999766 +15.1025411991726,6.85792462940558,-49.9049049110327 +15.0770064500623,6.83350669627085,-49.3568417152236 +15.0493498826668,6.75026547246783,-49.1680018320929 +15.0153965266556,6.66444753357598,-48.6655874042381 +15.008002339418,6.53131755396728,-49.5839218827942 +15.0273786172388,6.54703171067822,-49.1477732826144 +15.0103576592057,6.54621461142536,-49.1956291443607 +15.0133635757501,6.65275295922276,-49.4872736697572 +14.6619372895209,6.96524675670328,-49.3634703258929 +14.6140256094828,6.85504249754114,-48.3336332381775 +14.6328312125322,6.93428852823025,-49.3894678470679 +14.3247050288257,7.08769272046032,-49.7019235515595 +14.3181463479566,7.06046655422555,-48.948778677529 +14.3101830467565,7.00860237468431,-47.9009955417407 +14.2343290050358,7.23764086658587,-48.8303398721288 +14.1890817798782,7.42813649367504,-49.4623390559411 +14.6056901233614,6.75927594367757,-48.43848661849 +14.6323685392237,6.62542141053285,-48.4808660321951 +14.6634004369557,6.57711786042054,-47.9625903074752 +14.6822253236247,6.53256297850199,-47.5741599994808 +14.6647907058529,6.63148801166511,-46.8879281069828 +14.2897988059972,7.08259205877637,-48.3384086954622 +14.6465222391392,6.6620140691533,-46.1712995882911 +14.5917670708063,6.87620069665569,-45.7294935567397 +14.6243193137614,6.80278589836744,-45.8311435813793 +14.60950109904,6.88487167593984,-45.8961622728227 +14.6209412948586,7.03867902114168,-46.382101211435 +14.5933522593647,6.95363578627466,-46.5230762381659 +14.5455736393994,6.90505734168364,-46.4194249735652 +14.4883750864435,6.88361544540549,-46.1830203556197 +13.8892018994575,6.87458395286738,-45.4402132972512 +13.1223772782442,6.75562099435062,-45.0652890613193 +13.5063197606455,6.59686626000147,-45.7557532312183 +13.7563872817879,6.51181752437447,-45.5338605568993 +13.2736517987708,6.6353822462161,-45.1152405818557 +13.9856290519828,6.4659337840842,-45.1889371717693 +14.4767531339533,7.81189063730412,-48.4124865358412 +14.4698586686166,7.8154062603401,-46.9869914241254 +14.4632641129593,7.84207316538076,-45.6017682086145 +14.4398667739904,7.74003059783215,-45.3381547024158 +14.4526607420151,7.73656265818188,-45.3955230495445 +14.4765404796936,7.62246250314682,-45.7828378617135 +14.5283703680421,7.4097379194971,-45.4435396654181 +14.5805703326304,7.33034268509219,-45.3007648618474 +14.625254619658,7.28734072201773,-45.5023504398089 +14.6292859260881,7.19807312153816,-45.135729982554 +14.6068447902478,7.34284891803291,-46.0715214320749 +14.5594617009351,7.45250625406176,-48.4826819946219 +14.0152386771427,8.25363488729433,-49.6161237694008 +12.8031565386154,7.04087061753368,-45.3534138177178 +12.5401095122157,7.01388572983746,-47.5809380444345 +12.4771590344192,6.97292571875031,-48.4439623600075 +12.6626035778664,7.0303294547065,-47.2504070776454 +12.4504593501783,6.86776618503391,-49.419167660289 +13.6791859724939,7.57857768266392,49.2183767429339 +13.6941917477931,7.49105891652139,45.807545036799 +13.7353559219897,7.56315432373914,45.4151063794762 +13.7863984257927,7.63716048818617,45.41637959422 +13.8275602312628,7.63761708112435,46.4008335883559 +13.9016072998369,7.53407720117045,47.5035445191384 +13.6839911502245,7.51688393396185,47.1476311794066 +13.97522376254,7.43609200442511,48.227019049005 +14.0717858877949,7.26820545179131,47.6526685737228 +14.1844370127789,7.28956957756451,48.2199594334799 +14.2646521651765,7.29413693230278,47.3587098559492 +14.3386352421835,7.33561195472432,45.0864304466623 +14.6183375669901,7.7621439907652,46.3605049222726 +14.728323314817,7.64748103025775,49.7767531548158 +14.7666992160612,7.41995469875339,49.8398130796732 +14.6814751471087,7.74834519789948,47.4300733195509 +14.7299273926559,6.79933296419856,45.5032288001045 +14.7514011124026,7.11307056942444,47.5505399857724 +15.9902499603105,7.8586001720855,46.332347490053 +15.8098255210764,7.75977179589753,46.6079364563469 +15.6211085373997,7.67892460697833,46.3119855313691 +14.94264527113,7.77685193536814,45.5296140523117 +14.6422639145022,7.7541242231425,46.6723986617002 +14.4314391075258,6.86038050367184,46.6575010822365 +14.4380691472522,6.81857880544894,49.057768964648 +14.428652818506,6.76759950575232,49.8714217654021 +14.3893394418086,6.6991941323696,49.6745033552234 +14.3896385729695,6.66543222178069,48.6533588384133 +14.3856379931373,6.6432521848714,46.316939603855 +11.7802057098631,7.45621469579453,-45.2249218489478 +11.8414940377338,7.43751198291225,-45.7888999690601 +11.9010083261523,7.39691376635157,-45.9467542581771 +12.064885484464,7.40675894171133,-46.9559961942148 +12.2036121666518,7.4865315096724,-48.5035421100271 +11.9968368516058,7.42690910777149,-46.1333818060432 +14.129862935562,6.99852375034701,46.3988598239613 +14.1086835558121,6.88061469822054,45.7288069692486 +14.0786244383512,6.80205828658895,45.0525068810588 +14.0512871086703,7.53529812914843,45.5081798334878 +14.027422862927,7.95860838321873,48.9537519721861 +13.7522535839332,6.64492338141612,47.6434898698541 +13.753124731342,6.73629683029572,45.5233192678194 +13.7559726978976,6.80099191006789,46.1402582858337 +13.7296392132491,6.95601554140343,47.5713461558988 +13.8036671403723,6.57715732010087,49.1649114207617 +13.7294082996668,6.65582957732946,46.4049878176118 +13.3505110200322,7.52280130856926,-49.9020952522841 +13.3096571905205,7.40670302109973,-48.6995959377307 +13.2632612227178,7.38619327099645,-47.3854496221581 +13.2474681246481,7.48812064986985,-48.1821179217596 +13.2503636987616,7.79712788638128,-48.6764159877326 +13.2496279128764,7.56036166266733,-48.5679167951936 +13.2828208172388,7.94680998756545,-49.1580655797408 +13.4419850438902,8.26891438277926,-48.5090219213696 +13.368657840907,8.12973096973549,-48.9681932510775 +13.5290178256716,8.26793747445658,-48.6109185115226 +13.6338195295251,8.15294866707327,-49.2716207383456 +13.7675748473117,8.00206344486237,-49.4105345060088 +13.7101421604932,6.90900120152703,46.6311666596064 +13.5018456551075,7.14472921450247,46.1682075641745 +13.5016298209499,7.25653608733669,47.0344812665225 +13.5347552578639,7.23054577082978,46.0154218505791 +15.3303613081025,7.93340660210848,-45.9528800638308 +15.3164371103044,8.05874981688514,-47.1241065027896 +15.2956831923118,8.12823200822302,-49.9758122507411 +15.2335143818568,7.93053058647895,-46.7907793995867 +15.2499211471939,7.93727124405986,-45.5354014263652 +15.1874103864241,7.84197881462028,-47.3012472625968 +15.176711670982,7.73480307696214,-48.3852435574861 +15.1852032290953,7.63375657601802,-48.3356717781786 +15.1541483582986,7.57617207572045,-46.4930308503394 +15.1649927927414,7.52649397595649,-46.4833694817209 +15.1794411512276,7.55311417206029,-47.1750006813031 +15.1871304181492,7.5117334420507,-46.4909409221341 +13.4611744374082,6.64134343850962,46.4231276569224 +13.4526504188863,6.52103536342205,49.1934121467532 +13.5360368338334,6.55090042840359,46.5501664251978 +15.2254763659807,7.54957968669351,-46.8367664601267 +15.1708998630288,7.50199478444188,-47.7557208301528 +15.0483610639897,7.44597107667782,-49.8126510686581 +14.9087468264956,7.28294421036378,-49.6574841494283 +14.7241543590552,7.22763981129518,-48.8968226338826 +14.5010231538373,7.20724564427974,-47.6211550578491 +14.1086572643624,7.17934116049891,-46.8070943846417 +14.2952725403367,7.14569011741115,-47.372012782318 +13.9666435871527,7.18481239880856,-46.0212995722198 +13.8948474312965,7.30434639242944,-46.2363951396182 +13.8272712221456,7.40206735994224,-46.5355392911603 +13.7151717608471,7.53408709402361,-47.4628431797171 +13.57920725296,7.65924244371265,-47.02707018817 +13.536068527612,7.69686517821025,-46.2492912217683 +13.6263312116241,7.60135517202316,-46.7673332845807 +13.4934449062924,7.69748069939972,-45.8527894822199 +13.4127283046066,7.69336780520545,-45.2621676651373 +15.3526934407234,7.80916473243238,-45.0150398233158 +15.4547087367846,8.08968070355037,-45.6820970756492 +15.3981320117768,7.9463970244807,-45.9690300985655 +15.5359041531503,8.22267657818057,-45.3573063411123 +15.6325301133924,8.43299926439558,-45.8003609216088 +15.7482738769341,8.75049863050305,-46.1297499280297 +15.8910890745691,8.90337976716033,-46.5877826566787 +15.9996651688579,9.04805532776264,-48.386719892369 +15.9534827705904,8.97278587111955,-48.4031087775165 +16.0011247460841,8.92101668415914,-47.3648799717526 +16.0117212613539,8.7888641240261,-47.3735733572308 +15.9595669484161,8.51228069063895,-46.5617062707472 +16.0010347940124,8.58621691544773,-46.4020374287424 +15.9547171038581,8.398185483577,-45.8343371690711 +15.9353288562528,8.42163987066206,-45.9159481830442 +13.6013223148726,7.23923796248215,-49.6946676485323 +13.5420927746896,6.88650761511738,-46.413717616735 +13.5670890449632,6.87699702342462,-48.8588855505582 +12.7640836218625,6.66223345203378,-45.3557137640499 +12.7560364441515,6.70174701166717,-47.5349106908465 +12.7678832851077,6.73460935555224,-47.2324023168801 +12.7067144593502,6.69507352956076,-47.7993211106058 +12.7387863831923,6.70341363682554,-46.8435786213266 +12.6808244815463,6.6170642882233,-49.3639995210202 +12.7777524527989,7.2219242830047,-48.8095651902363 +12.8106954194618,7.45899128787192,-45.6115807462469 +12.8183547518492,7.4445296496655,-45.1941650298148 +12.8511289215865,7.4060704496598,-45.2379224361191 +12.8649474300027,7.49522797685902,-46.8513321954498 +12.8864875045569,7.41544564206793,-47.9314670563998 +12.8999041315636,7.3125647663774,-49.7133384115138 +12.8824105754195,7.08048893224496,-49.9658356377461 +12.8716158614559,6.89074077688094,-48.8468805840552 +12.8855648647643,6.72213894150222,-48.2403194093738 +12.7851670550587,7.50819429716591,-47.4762813527063 +13.7664314140168,5.86841644208489,-49.6602691028301 +13.8519128573481,5.78669388195574,-49.7927980086257 +13.9855758062989,5.78257268993634,-45.5414829069053 +13.9396262432028,5.7775114195439,-47.7733812182519 +14.7454815127508,6.99214454971043,-45.053245734342 +14.8094121835945,7.11604492246923,-45.9052213960449 +14.6877367918753,7.0687910038144,-46.5695841584494 +12.884698157145,6.84092270163089,-48.1110542108307 +12.8923660755495,6.90947416822951,-47.4856854004314 +12.8483595765244,7.07979660783452,-48.6863701176596 +14.5798924976275,6.92587699276023,-48.2650367853914 +14.6175965654921,6.79772198936934,-48.9185172992567 +14.6532210739504,6.68564295811214,-49.20583642988 +14.5420827671247,7.03661719483603,-48.1800072999265 +15.0035016444844,6.22455579066679,47.3245372347656 +15.0592602659769,6.31794131247048,46.6923323125597 +15.1631823387249,6.38435230794903,46.0601292367739 +15.2044077467309,6.32039470276331,45.9115298085209 +15.0885240567083,6.40306218200639,46.3185653360122 +15.2383481015712,6.18036418673299,46.6790870595677 +15.2474631601949,5.82080331232974,47.7338350273017 +15.2653959631151,6.00928707848371,47.0466522185027 +15.1408347681822,5.59832327416521,47.8428644956883 +15.1824516542274,5.66612309137349,47.9874159545382 +15.1716532627084,5.68758080391353,47.1231967018039 +15.2982127806693,5.8885449211283,46.1876645824572 +15.5041137764565,6.1564595111502,45.7444378926467 +15.7705896926978,6.45813067161965,45.5756056489777 +15.777049892922,6.73342557687579,45.6097920149058 +14.8330705328286,7.08243098977085,45.8607862541483 +14.7348789638225,7.11167750111901,46.4931340114757 +14.9403994191168,6.94838425766991,45.828035043745 +14.5056521763076,6.989926508145,47.099682173074 +14.6345366706951,7.05467531651993,46.6744205930673 +14.3530821826306,6.9108749628491,47.4160889275979 +14.1110401241485,6.96241186606339,46.2625169277071 +14.2157229220344,6.91554823490812,46.6468472963285 +13.9597689174465,7.12810441625373,45.9937841793874 +14.0226186351528,7.00002147050124,47.1533161034891 +13.894035388913,7.29306540910191,46.6210934209816 +13.8128179540358,7.49523311221494,48.112791719734 +13.7378944860657,7.56865264362148,48.8661387843622 +13.6349448144455,7.56703189707549,49.1412178829285 +13.5187336927726,7.50670209395096,49.3430083854871 +13.4149738358163,7.32756335505587,49.6198113247668 +13.2935779898091,7.10190132404629,49.3614360593546 +13.2077748344041,6.85258499372224,49.5549890810905 +13.1109448859546,6.67181574336221,48.6104355921929 +13.0268327073395,6.64766905938861,49.1333226009073 +12.9302936070446,6.72176373683718,49.9819161763644 +12.9038501176634,6.81991927833464,49.2142535641161 +13.1285605892448,7.31395716925967,48.2375825147333 +13.0454415873154,7.42739784626145,48.7767976079396 +13.2644471052494,6.91843967820089,47.3035274705903 +13.1863526949306,7.11152863187838,47.9973850856625 +13.3377974402809,6.68785087637322,47.0209236696552 +13.4962950660131,6.43307398805957,45.9375106820312 +13.4051329816073,6.5584432511136,45.9215103129918 +13.6237318759347,6.37674044922749,45.1242525429794 +13.9704313403999,6.56123759814247,45.1685039858077 +13.9964828176998,6.604917549647,46.75230158025 +13.9693704261987,6.5897863147864,45.6656170916895 +14.4257345109104,6.5196536696334,47.8592123304123 +14.0433326937924,6.42836882492098,47.6941100013182 +14.0605182443878,6.35298344490448,47.5546015443847 +14.1467962986945,6.35420898278135,47.8733656559462 +14.2876475507773,6.41349235613136,47.7340561926177 +14.3921373387886,6.54983405193734,46.9919942495239 +14.4496773086923,6.77915524499501,46.0198306349462 +14.5025840381654,7.02669747194671,45.4531646764947 +14.4144975022336,7.38481093774172,45.6525564294229 +14.4260241320178,7.20316608624173,47.1989832307989 +14.4841711391168,6.97842098557618,48.5172975801046 +14.572826215358,6.83480280578547,49.6816677842487 +14.4801684591506,5.87027973008838,-46.8960543734681 +14.4449336232244,5.91516658916657,-45.5933134788409 +14.5569903282697,5.77867651598044,-48.0642821068466 +14.6332715886182,5.86646781804119,-48.9343418791802 +14.6951256957915,5.87971789177315,-49.6776119550202 +14.862255698992,6.19141144621909,-49.248030127903 +14.8686965588334,6.55530882503179,-49.7453486903015 +14.8759835234149,6.67943900262208,-49.5840035212215 +14.8698523276643,6.27290523974167,-49.2763292890129 +14.9136467532959,6.76720661634934,-49.3666267640936 +14.9433851273795,6.74448441323722,-47.8238012909634 +14.9421002692771,6.68258302373739,-46.784089577311 +14.9361515720504,6.78810624319533,-49.3405234605746 +14.8741196709084,7.64858058088877,48.497237897038 +14.8234219021477,7.66207697052902,49.5779243115062 +14.7783065113558,7.661157246002,47.7404505130941 +14.862181633604,7.70982031084148,48.4749352772661 +13.9677481479118,5.97305523250391,-45.3089367439463 +13.961495097007,5.98614592852437,-46.9599906452098 +13.9601892245124,6.00356802872905,-47.5190842393164 +13.9485718692997,5.97166488835484,-48.3844695648029 +13.9580642813905,5.97895857472795,-45.9364821749725 +13.9380319488879,5.96685463974891,-48.9752049592065 +14.7174985524236,7.62828728152293,46.9380003016794 +14.6669751045183,7.58705882695434,45.6400404016573 +13.9202387665111,5.97315020024261,-48.9714511321873 +13.8839640892789,5.94916601046124,-48.6534559084545 +13.844150580825,5.84033716896194,-48.2930407045278 +13.9007103238128,6.01508691324653,-48.9791481504447 +13.8604637606093,5.88531934744936,-48.3369352529028 +13.8007822838166,5.8281286428301,-48.3693939591758 +13.7379144243711,5.91355450086322,-47.9911764039961 +13.7130245956734,5.87529617539039,-46.8511380463787 +13.6792400828744,5.85920931269349,-46.6051042068002 +13.6402688524664,6.03766801655194,-46.2915453004874 +13.5883274527513,6.23443725003021,-46.4519621192293 +13.5177614849015,6.25895812311161,-47.3780817293335 +13.4375486373074,6.26760595891794,-48.8995478980659 +13.7523504153405,5.87678027800362,-48.1401654094949 +12.0809099092855,6.6895770550872,-49.7329328955971 +13.0430805904065,6.13457032243557,-49.7631085512909 +13.0757667721227,6.07366649869028,-49.8634540084503 +13.0734941165704,6.03592456190436,-49.3850835148224 +13.082796809016,6.01747466966361,-49.6071080199481 +13.065581938256,6.39437750770979,-49.4921032236219 +13.0392094654562,6.51308518741188,-48.2361034035578 +13.0339107393871,6.70070725174862,-47.376923913664 +12.0422296914706,6.5842903798467,-48.6146204857416 +12.0115080322716,6.45323392846678,-48.2593922910982 +12.0258975278641,6.25934498514577,-47.5649899361375 +12.0491891818342,6.21556253670956,-46.708254973765 +12.145352135915,6.23202926001832,-46.3065136060613 +13.0693333992898,6.19740308242989,-49.681168959515 +12.187389202166,6.24264723806777,-45.0122914127804 +12.2600345063134,6.35902303575648,-46.6586589213122 +12.4503860656193,6.73084405265967,-46.7418706114463 +12.3631500401685,6.5334984089301,-46.5989676676449 +12.5263292302295,6.91807067562634,-47.4380273730384 +12.5970654061229,7.08649792845801,-47.2751066404202 +12.6984812577671,7.12241527419892,-48.8267767937075 +12.9401311379015,7.15428922330099,-48.4054828041514 +13.3772211201343,7.14486509404285,-48.3664067407357 +13.1917508951899,7.16737240731462,-47.6684345506651 +13.0861120740574,6.83037330827629,-45.0530051499185 +13.0527968962797,6.82594160906013,-46.2193909937966 +13.5495282776348,7.16490600811815,-48.1495692970677 +13.4973728960579,7.17396781054666,-48.4154616085589 +13.6591110473405,7.18296204327545,-48.6677160007808 +13.6108680987399,7.17064439245951,-48.041137009441 +13.7480541110371,7.19966113056699,-48.9230263854268 +14.0138176568051,7.19677944712838,-49.1936167127745 +13.9415501630002,7.17824057169105,-48.8550090149867 +14.0644033396902,7.18453551356003,-49.3200957482186 +14.161955930454,7.09311818030948,-49.3547311755053 +13.8190514217464,7.24484127994696,-48.3140927743895 +14.422187904806,6.9424056161995,-49.6457965783151 +14.4924166465292,6.90674580871709,-49.4533515519834 +14.5392303136387,6.82249247812787,-49.5370084097608 +14.5437550462784,6.82530069220042,-49.1090768613951 +14.2631413268915,7.08365099068739,-49.2958825496623 +14.5373047347562,6.84985086945439,-49.5326372782898 +15.3565618579639,7.56403179623195,-49.9953336753059 +15.4678588536261,7.75214386780733,-49.9057186669315 +12.8298371118491,5.75654181209891,-45.5865227215572 +12.7848799467367,5.71297783569389,-45.9822987108631 +12.7319454135229,5.78788488599194,-45.8252552212207 +12.6912726429927,5.79762379222875,-45.522197083484 +12.6486814517089,5.80843278850853,-45.3665297799387 +11.7631318463651,6.13429124185495,45.7968096208906 +11.6296225808193,6.19803900590254,46.9512595696768 +11.5339920760691,6.24451379505642,45.8549409953624 +11.0238687666756,7.22297138946369,46.6219673224203 +11.0046864435973,7.47390420500512,49.3081207648448 +11.7095372248355,6.1769010042973,46.7179555610297 +10.9963070758372,7.50139921702009,49.2003525383415 +10.9830126031472,7.45102525020089,48.424319519129 +10.9305012046762,7.07616223547278,45.0194851780788 +12.0685428618586,6.71054396544449,45.3933389366524 +12.1550041940405,6.6577370889574,45.143736149777 +12.2523615390595,6.19473547807046,45.383685485098 +12.1829729010006,6.38945928037466,45.5982812082627 +12.11660104419,6.66471908549658,45.3201226870786 +12.0564362371828,6.98437930505089,45.1618298003497 +11.7450937475082,7.35737914164523,45.8049698368874 +11.783071811223,7.41144484136078,47.377356184153 +11.8016456563314,7.43060389848429,48.0816683086677 +11.8091012967843,7.45276481321814,47.193860378839 +11.7181087782054,7.39827997677808,48.8324717491054 +11.7749347423004,7.41640963376625,48.4674821426793 +11.645913139783,7.41899748171016,49.2265202601366 +11.5833610593223,7.40599108322722,48.8515382360036 +11.4636916127922,7.52426638336155,49.9603088856931 +11.4577248829874,7.47236760331974,49.3910499248971 +11.4611397564505,7.36707252458205,48.1269799133204 +11.4723498218056,7.30288807236921,47.4409039680649 +11.4749147230431,7.29364051248909,47.2732743934085 +11.4103564572879,7.34494895735641,47.2701144618003 +11.3454061962424,7.42946721373703,47.172104095515 +11.2809728805361,7.58082166668259,46.9036345747383 +11.1449385223499,7.7854390392447,47.3052651086174 +11.2024789250826,7.66182582968194,47.1194884122255 +12.8427213123947,7.20152957672253,45.9868378770587 +12.8092990851085,7.20240527099068,46.2786001120779 +11.0878156591407,7.84683982816744,47.589898929187 +11.0199305884788,7.90358425873408,49.9051092156203 +11.0536053559634,7.91458461585816,48.7089517679806 +10.8411908367141,7.55135489251863,49.5944966969604 +10.8139698430599,7.64380740537512,48.2899860829001 +10.7400347082292,7.96945012932443,46.8493950988436 +10.7765205700331,7.7975075526273,47.1605877053466 +10.7180415156191,8.15407013644962,47.3915437074826 +10.7218555479427,8.10060094182352,46.9431511876301 +10.7401997517978,8.04223863278134,48.2008897623747 +10.7324391394746,8.13910455393878,48.2089217572932 +12.8081911436412,7.13259073051287,46.3498863066047 +10.7107596980298,7.80603545816974,47.1551369514401 +10.672570228441,7.65943697598845,45.8776891093913 +10.7200758447513,7.92846567639926,47.4685688562059 +10.6299248154062,7.54911934360502,45.858949575175 +10.6047355995692,7.57187841827518,45.9314564725878 +10.5990285091028,7.64860775942481,45.8906154118474 +10.5972964866437,7.7122794609713,46.0567031174807 +10.618177916909,7.81480278526818,46.1671781150553 +10.6637180904627,7.98656481152229,46.3188644219512 +10.6913062252224,8.16974612032905,45.8596845291594 +10.7164439681043,8.27708985770734,45.5580393574255 +10.726526549909,8.33443839445392,46.0161678222792 +10.7481711804137,8.36057952246099,46.2487337235977 +10.7377817887592,8.26256042676188,45.000114465364 +10.7223140193578,8.12291869922552,45.654276174293 +14.774536219966,7.53824669434864,47.721541402839 +10.7395064293733,7.97554221006241,45.0380402661519 +10.7719266808064,7.82350039000157,45.1658646717477 +10.8885473021642,7.53180020623627,45.2295942396074 +10.9660161378643,7.47653261597675,45.2090698286938 +14.3101374909535,7.76904087579105,49.4340790010778 +11.6593996260583,7.88691785780837,45.5660854171573 +11.722450787514,7.96167991349881,47.1627689864665 +11.7635121015845,8.02876092149023,47.7273522399198 +11.7869118412078,8.03691361953965,48.1173883900523 +11.773417149738,8.01465304558134,46.5867781582998 +11.7244542226978,7.99976400206347,45.6692890619243 +11.6757740613345,7.98301491665091,45.1421066178045 +11.6694740577975,7.95367698754847,46.2995309406083 +11.7055984342637,7.91139824926283,48.7870361778173 +14.2620476697112,7.760623815575,48.6746557195427 +14.0993698712771,7.5120526327652,47.9328951452206 +14.0171866604382,7.33388804983377,47.3830784523197 +13.9188594227703,7.08493584606877,45.688246015363 +14.1617550291971,7.61742519006382,48.3732609783304 +12.6854123956769,6.65155988853899,45.5979187766377 +12.5952985518203,6.73352448718277,46.4258879958816 +12.3691593748467,6.84661168606415,48.2476878820115 +12.4947824127114,6.77581466987536,47.4735063522255 +12.0641203099058,6.95604856734996,47.1383376760057 +12.2298739512907,6.95114334032942,48.4659891795897 +11.8952361740472,7.20865450546482,47.3045896430323 +11.9638563611779,6.99440395443783,45.9077997130271 +11.8803610595356,7.07336983629348,47.8596167661929 +11.8461088643577,7.11534779869068,47.572455094029 +12.0417283668894,7.00468795949531,48.6981504837931 +11.9383390331875,7.00985172849166,48.8958948867714 +12.082519683607,7.05362359651961,49.2050981074559 +12.2165283784623,7.10841096624293,47.4466299984189 +12.2851279195534,7.17650106678834,45.1676510254494 +12.1463417001127,6.98157887113187,47.4765875662806 +12.2582049666376,7.17875092022553,45.0752962860563 +12.2788894961151,7.05412826565046,45.4707852665309 +12.4135902932523,6.72703303877005,45.2321575660363 +12.3043224294008,6.95198716541331,46.2119732554774 +11.6753023210421,6.15066217309723,47.3347119654955 +11.8626898359601,6.97290303892378,45.4297227924237 +14.7244384743014,7.48005890691799,-45.3016163829404 +14.8795875692064,7.83597749958489,-45.5543437095532 +14.9432275983393,8.01266842421043,-46.5549458656343 +14.9650162598405,8.1166189229185,-47.3009510820993 +15.0182021932184,8.18492096220376,-47.704952979817 +15.149241571368,8.29119683359026,-48.8901828670606 +15.2238199021188,8.34366415984084,-49.1039679806418 +15.2774687383601,8.41156146304386,-49.2383797927414 +15.2659482741265,8.29521554496725,-48.9063802511943 +15.3216281682405,8.36456856002692,-49.8378695536976 +15.3054580902689,8.27148653404457,-49.0945572634818 +15.2533586483179,8.34783624102767,-49.6782910404698 +13.9744179641613,7.56901933731853,-48.8694225111805 +13.8440133246502,7.35300349347923,-48.5973051202896 +13.7282298397034,7.19142075184382,-48.8340992629406 +13.5978798039517,7.07197282795319,-49.8086840827107 +12.1067810026421,6.38023084584167,47.3582885254086 +12.0291137219656,6.36557933240469,49.7026513612191 +11.6696695185935,6.12234519539692,48.0371590546903 +11.5652532212739,6.14532657671636,45.2436611368397 +11.0159319237513,6.65110554967287,45.9050808018127 +11.065346534064,6.57143554753774,46.5296440709825 +11.1774532517736,6.45286231755709,45.8286907838855 +11.1143941609372,6.46034009532551,46.275298460014 +14.1221293010304,7.87073492721404,47.0265481378446 +14.200076829735,7.74700295120552,49.2363722523318 +14.0154365471678,8.03305672526061,46.4590084785515 +13.9184551193583,8.10726810562684,45.8729623870405 +13.9546927510201,8.08263615793602,47.1478737486705 +13.0371726824197,7.09877062082832,45.0985957214991 +13.0160876479402,6.88819412415607,45.6109507812401 +12.9778842603864,6.71900424168636,47.8650875053827 +12.9084873999921,6.67528278573241,46.6401477556451 +12.8484147403323,6.88296333508536,46.4267007759532 +12.8801849336767,6.73640978181687,46.6081616597155 +13.054962089186,6.9939544069352,46.004386817171 +13.1038705485781,7.11210756471511,49.8461046912502 +13.1214760741102,7.10127959674654,48.39935556109 +12.9309056152987,7.31204623821255,48.7092662806476 +12.7751766650876,7.14821132098182,48.5012265710301 +12.7161235477286,7.00983280529773,47.5422557317241 +12.8619999396372,7.32740023208165,49.0604017190404 +12.5514772975157,7.01355111544916,49.430298689462 +12.6485154250463,6.9312571490133,47.8005118438854 +12.3208156866093,7.61251907961029,49.4456801560732 +12.3842090785032,7.74653821448812,47.6235652758146 +12.4760367450582,7.77956616296911,47.2791029037052 +12.4270183695455,7.77965197470438,46.2429962994571 +12.5685985694745,7.63326437915892,46.4241492884368 +12.8220308853388,7.2389820514152,46.2384300334745 +12.6744000508559,7.43563871710402,45.646434779203 +13.0975815599888,7.02414853000869,47.046715937832 +12.9804202426158,7.09844204557174,46.7742582165644 +13.3705178315918,6.96922189219991,48.7994426699932 +13.1889538714493,6.93969095336387,48.5238857823162 +13.2726733067411,6.95106421180973,49.1057963571988 +15.146925181123,6.77716558793223,-45.9820496417419 +15.2378651347413,6.72696150171628,-46.4733762077168 +13.7379501821882,8.0687446226886,-49.3754272944672 +13.7144700915186,7.98305535573208,-47.6604709245597 +13.721509910142,7.84261430537796,-48.766358635572 +13.7265376035323,7.69132444127207,-49.219994780607 +13.7111318342591,8.01911019273828,-48.7090843645959 +13.7011749806882,7.48956283351333,-49.9359024916275 +13.6922118627484,7.40623090454618,-49.33614439079 +13.6660361939845,7.37754739773624,-48.5413995522532 +13.6298730410206,7.33725909505992,-46.1449112934384 +12.4523426466992,7.03593256721168,-47.5065489106874 +12.5549215951554,6.90726981590346,-46.8377371146804 +12.3390667695844,7.17290813627513,-47.6318460108821 +12.2585612562889,7.32701881292946,-49.4914079958077 +12.1874706573602,7.43217412878824,-48.2348048587324 +12.0851936890018,7.47436518172325,-48.3442220378825 +12.0121127848697,7.55652520199197,-48.2095613744779 +11.9370375766446,7.40335877521288,-49.1428401753113 +11.9269970178096,7.27247169892377,-49.6617590745991 +11.9061183324721,7.16699848552155,-49.7286407187378 +11.8848926901924,7.14860746776098,-48.922480093904 +11.8723338300452,7.04484007544924,-49.5898660216488 +11.8990706046711,6.99276849322484,-49.0033680183754 +11.933979956895,7.04022996528878,-48.7936362678787 +11.966921198562,6.99674393225309,-47.6805809090965 +14.1956465578344,7.97084016485887,47.9007227664909 +14.052133343903,7.84710478870105,48.1016270398329 +14.3651362698682,8.0049314944651,47.2458291761073 +14.5243760805859,7.97770559634526,45.662348161261 +15.0257232545146,7.98421902309893,48.0274882208807 +15.1414549013022,7.98523121462601,49.3235684888263 +15.3815650594335,7.74930239840893,49.1224415831853 +15.4217354602869,7.70107033809709,47.5947971860506 +15.4721076579799,7.63823983424395,45.3317802084095 +15.7640518666592,7.54892717158147,45.6657134756013 +15.6576259520682,7.6764319242072,46.9771864141624 +15.5919776991961,7.76923382103734,48.8754250718306 +15.4075934049175,8.01176268491336,47.8009303570931 +15.5064476850702,7.82033762382352,49.4671122787691 +15.1628936637989,8.07268823634,45.5274993248853 +15.2798006459183,8.0350707057854,46.6292829170972 +14.8492192821426,8.12800255667112,46.4709173195796 +14.2210998185982,7.51817480437737,48.0920314159608 +14.1067161936149,7.45021700865869,48.0331609578849 +13.9876086753381,7.29830783147136,49.9038994210845 +13.4215559157258,7.36393035635173,47.8637092039396 +13.377959237887,7.41067515547874,47.8591276622969 +13.3273584592945,7.38070170692826,47.9883223351597 +13.2384927938705,7.31302892926616,46.9883061021571 +13.0303041231078,7.25811692884456,46.9316873398982 +12.9778140002509,7.39415799905417,45.9435863034535 +12.9310887329869,7.45533317109533,46.2248118989649 +12.9210290089591,7.40324629775129,45.8562453462014 +12.8939335311911,7.34811890565778,46.8735945614633 +12.7949498290157,7.34171523627546,45.5174702006347 +12.7977570655539,7.513657704385,47.0028391728814 +12.8000729229697,7.39977657706653,46.1657242552065 +12.8392266867438,7.59265707372041,47.7493071207803 +12.8288503616497,7.64413664304811,47.0350310253214 +12.7934663778838,7.33501870989035,49.5323687362429 +12.8381422148472,6.92817954896231,-45.7958846833156 +12.9207908197438,6.98447255142058,-45.2108090662223 +13.1370544687971,6.91387251310071,-47.3677809726033 +13.1706453339385,7.06792491478412,-45.1848726514982 +13.1535442403018,6.8338258142877,-49.5610956829275 +13.2221910597208,6.97142618678468,-48.1301752985054 +13.227435115636,6.87726576451511,-49.9976384765108 +13.2047654563375,6.97337494815373,-47.1533501584769 +13.1985800777053,6.90079261253564,-45.3341983558972 +13.2933524179549,6.45913702791312,-45.7206863872637 +13.3987450135413,6.10087961238188,-49.9978474469819 +13.5245766922736,6.14408420329906,-47.5265083836606 +13.3279802254356,6.37369561050991,-47.2123453130604 +13.3529297609225,6.20299065353508,-48.9220168732934 +13.6947308261975,6.47139617055876,-48.4953509234943 +13.713455348239,6.85741051713402,-48.651053561637 +13.7384573448206,7.11793290111769,-49.2450723866445 +13.639740326929,6.23492027443017,-49.7780901280593 +13.7211508396181,6.67794776674301,-47.805687176289 +14.8802992233397,7.25076509886189,-49.5960430178228 +14.9731224468784,7.06589647620638,-48.5325736007939 +14.9938686021757,7.02609895272956,-47.9566618036765 +14.9947039139806,7.04989300093522,-47.0699369822065 +14.9408203631356,7.14062738925996,-49.1473170332973 +14.965526553062,7.08262875169294,-47.1011347431743 +14.8825199274066,7.21632277772796,-46.7454361801922 +14.9196896947362,7.17826271524554,-46.6846940017949 +14.7436714824115,7.28828216811322,-48.2346972570317 +14.8176430142543,7.22895188191683,-46.9949828673911 +14.6769759944773,7.22104327639978,-48.4667416852335 +14.6263112849408,7.13817671424943,-49.4712773658464 +14.6556466795564,7.21523248094687,-48.5174965796789 +15.613184828068,6.83691979143094,48.1290582038144 +14.8742759905127,7.11676363910586,45.3204313474337 +14.9099427982665,7.21776690887966,45.1826858465111 +15.0389607766245,6.29981080035571,-48.9836007439645 +15.0337817235442,6.31001297015116,-48.6151669203514 +15.0712271853026,6.29418535362488,-49.1004390245297 +15.0957600783564,6.02630044310902,-48.4421139857719 +15.0915131556343,6.14624472976672,-49.0601671249186 +15.0684959484665,5.92775064739318,-48.6303417677628 +14.953682352482,5.88423181125181,-49.9301636908331 +15.0287440348636,5.8600487633736,-49.443122393342 +14.6343314231094,6.36213946990383,-49.757768473816 +14.6019266060705,6.42361656160687,-48.3213142682434 +14.6346379942829,6.56371387419006,-46.0605276570593 +14.7623783938005,7.13074394484418,-45.4230335781986 +14.7532042688624,7.09187240389572,-46.9494390189543 +14.7470535463651,7.07409147489424,-48.0944274134391 +14.7480119674891,6.9925684880426,-48.4196151938389 +14.7979190964547,6.951414053146,-47.7413164708335 +14.866345489248,6.94449200755505,-47.6011439260277 +15.029243019015,6.88289664409427,-46.4951810421864 +14.9513775496034,6.90311298160086,-47.4224598334548 +15.0984305506346,6.85235796552593,-48.0874667434661 +15.089875624932,6.82365655772394,-48.4228041076424 +15.0730216289663,6.87826488835943,-47.840020556866 +15.0489496524728,6.82062143955146,-48.1268010869424 +14.99568554274,6.87113000130938,-47.7202716909336 +15.0210198695447,6.79192828143819,-47.9977284588705 +15.0203906604669,6.82663780631211,-48.0135181073111 +15.0106190178364,6.80123429973814,-47.4475877391563 +14.9752286655092,6.78204907237522,-47.3543895222635 +14.9319337288756,6.72757271845633,-46.9596559359855 +14.8948317537376,6.7270763875029,-46.1315377413799 +14.8590086916009,6.70304553968945,-45.7201552995089 +14.8081051803739,6.78700427607752,-45.1684938005296 +14.7772140187286,6.75864391189855,-45.1509100552628 +14.7693378006603,6.8386148008651,-45.7071979651 +14.775540581583,6.97084347780183,-45.3464982637265 +14.6462404565475,7.48762894394788,-45.0036901169038 +14.5914115161181,7.60545802487489,-46.0111569550663 +14.3971293939195,7.77757827694061,-47.7478463699481 +14.4291887185932,7.83014708131028,-47.9213713282737 +14.4814420317556,7.75263386190422,-47.2906643821194 +14.3816616622178,7.96290230404938,-47.1046573463333 +14.2492716509359,7.85562718539856,-47.4975696203461 +14.3672270405195,7.95765783590501,-47.7184800372185 +14.3328431111328,7.81705717795457,-48.8721897050885 +14.3588349549666,7.95764951568471,-48.2620183088345 +14.2907241271901,7.74114170440049,-49.3096248673532 +14.2409913783232,7.48024911538017,-49.7184597883243 +14.2524499440878,7.53120301691974,-49.9039857065657 +14.5762956859635,8.15794912493325,-48.1763579035528 +14.5706449011962,8.2717520992445,-47.9750918060705 +14.5650380830564,8.0957121775316,-49.3534022610422 +14.4890132104517,8.35759133229324,-47.4321135656127 +14.5405484539212,8.36610790132671,-47.3836448614054 +14.3372946876593,8.40986640992314,-47.7339793340606 +14.4178394330251,8.41093938928575,-47.4915020123131 +14.2862451725924,8.32750944733294,-46.4403924050817 +14.2670892655377,8.17423054947425,-46.1508588885601 +14.301597337871,8.44686350170649,-47.6725797741292 +14.2307553530832,8.05241716512376,-45.2829731670334 +14.2088110703866,7.91242055953476,-45.6217705042722 +14.2663842669646,7.5455300414745,-46.9576003875473 +14.2384644656615,7.73212967808509,-46.4567187517872 +14.255886555485,7.44790382501946,-48.190016325733 +14.2376879251676,7.41141605646928,-49.1206870219596 +14.2316063837289,7.45277258121957,-49.8810125147822 +13.4742683144951,6.48872892881754,-47.4375324234164 +13.4985649431371,6.52693393687987,-47.6915083833201 +13.6158265263872,6.70695984519254,-46.9313983443633 +13.6607533764486,6.64878176303966,-46.8600396778155 +13.5545242990766,6.56893532280966,-47.1603846426399 +13.7198022101415,7.53396853808497,-47.8903899171913 +13.74496216046,7.70492625112132,-47.0673744780499 +13.7511425365571,7.62285887750943,-45.9675595724225 +13.7029482365884,7.3918563317207,-47.8540219972726 +13.6789730248605,6.9824634720488,-47.0108683228048 +13.6189832917222,7.89640449443817,-49.4320184240127 +13.6703653734299,8.00852121507674,-49.5459647759859 +13.2284075608287,7.17390393091701,-49.6311651641028 +13.1457969783495,7.12195306906856,-49.5513802331872 +13.7421495750661,7.16491911490892,-45.4776450703995 +13.7173765196477,6.98443974324519,-46.4528455325622 +13.6992042227499,7.0961303434614,-46.8237965465709 +13.7152012733735,7.33456627222499,-46.1495892005961 +13.69724314003,7.21281224620643,-47.7444724155864 +13.7190789065941,6.37129669847401,-48.5238408211045 +13.4257361440754,6.09403161135518,-49.7361165584011 +13.3898192640819,6.17334827878131,-47.8922560906214 +13.3238743066639,6.47380022819859,-45.230289693252 +13.2571621957152,6.99134810062201,-45.2010287173478 +13.1633693695487,7.33159701538122,-46.0943418644152 +13.0761605203261,7.7169755528708,-47.3445743730315 +12.955386035353,8.04781144687722,-48.5875307704083 +12.7479919663284,8.14806187805866,-47.0479591901543 +12.8615637877638,8.09720766264612,-48.5786890407302 +12.6428296994674,7.90767132097848,-45.256522295545 +12.2880416892708,7.87966244805539,-46.4847494351456 +12.1643753191008,8.18439423624472,-47.1650166917944 +12.2159157598374,7.98662973203596,-47.8340855747148 +12.0043916810482,7.37122165023877,-46.3605783305512 +12.0644224006713,7.34400273368832,-49.4409405974334 +12.3609123333164,7.55213902894097,46.5707711547053 +12.3577066792273,7.50144630846,45.3207376592696 +12.3206570802218,7.4391758277342,49.516016783867 +12.3471888918577,7.49793321098772,48.0190287313581 +11.5602149089559,5.98992158837901,46.976247011172 +11.5660034535309,6.15232219698808,49.0814943257832 +11.553419679454,6.0774856639845,48.4792248642968 +11.5660099849751,5.93558140991589,45.6187331172813 +11.3964421114113,6.50120580255754,46.3683083075462 +11.5432825642975,6.3277051800339,47.836535244397 +11.4847292706144,6.40205780451385,46.9210243952009 +11.5709865126543,6.22854064534071,49.1113592651702 +11.3045641715247,6.6724457103508,45.8214305253045 +11.1583070979388,7.32002941555742,45.5329414955587 +11.2537276523971,6.86691082655941,46.1838725450131 +11.1275380328953,7.55530798610044,45.3894872708028 +10.9920506629287,6.31217694675103,45.7113883860052 +11.0028025076931,6.23804300694126,45.6028682502602 +11.0451343351805,6.47042471045071,46.3975750018065 +11.0195713075633,6.34325095689086,45.8962433333829 +11.2031076640854,7.07337202056481,45.3726384925133 +11.3123307062603,6.80269556169299,49.0468195516879 +11.3011502893936,7.09652202022109,46.7071487965351 +11.2959361146916,6.90759813131884,47.3835264672125 +11.2937844009615,7.33324957551666,45.596232248386 +11.0746231234764,6.66294402536697,45.9381638874504 +11.1242890633434,6.83691540394007,45.20298446979 +11.171015696751,6.91544772498479,45.400180569198 +11.2051794500499,6.90020854392467,45.0628335695858 +11.2127935396717,6.82035279901047,45.6536707283287 +11.2192162978521,6.69003034866415,45.33141599073 +11.2215117222643,6.69291519788695,45.2800295991537 +11.2102398302854,6.98517050700273,45.0098922027859 +11.3090659763749,6.19087109475239,45.5360790514018 +11.3177024240409,6.10786990225345,46.0157658084219 +11.0783117024312,6.37351659106688,45.1707490724355 +11.0667970478368,6.61798198341499,45.7817886622677 +11.3166572690939,6.18689706058556,45.9786633655566 +11.034316345722,6.6900795683721,45.3280559150301 +10.9855444864053,6.71963070317316,45.7755266217486 +10.9343480496917,6.60416303869001,45.3531933532155 +10.894143771231,6.44525374716264,45.2711801216398 +10.8148172977766,6.45595489358942,45.3059244652666 +10.8293464283207,6.38044845583165,45.2338475028896 +12.4492271306258,6.85244718889919,46.1772748910795 +12.3420495231423,6.85493377828577,48.8489845954642 +11.6407497964091,7.48393813615747,49.6828365739711 +11.6249804451884,7.42604574789627,48.6807442039708 +11.6459606638336,7.29316478620116,47.4605778932034 +11.6695670798918,7.22224362160679,49.6417639677185 +10.9167379367009,5.80196953004687,-46.483639446177 +10.8230364321236,5.74696207176884,-46.6766899867883 +10.7739879536436,5.7318367608768,-47.3587857340866 +10.7530640309879,5.9181396619745,-47.3711391803168 +10.7479544827333,6.10573080703952,-47.5151036396238 +10.8164135335794,6.41504208300596,-46.8446820585635 +10.8572140260162,6.43072968382522,-46.221585727751 +10.9757427349572,6.28323197658394,-45.7144397832853 +10.9883887932284,5.89681820945078,-45.519800123543 +10.8893425884137,5.82248540484176,-46.1389702937688 +10.9304597425841,6.21199662675386,-45.9952280812094 +10.9661879730552,6.22663162670408,-46.6999610079344 +10.8782462977375,6.17751391688012,-45.9844768880881 +10.8838790339906,6.23463834054014,-45.5020452294565 +10.9128873796239,6.19322537300289,-45.5545227649162 +10.9737439298406,6.31672427307657,-45.6724948645968 +11.1200548278655,6.60857816627973,-45.3594582928454 +11.0868523637973,6.49555144059442,-45.719774953884 +11.1526502726587,6.63445956619001,-45.6526223182769 +11.1340259364181,6.56579057632627,-45.4796458061706 +11.1928374427649,6.48482843055879,-46.0074257652799 +11.2327931954683,6.53764943233466,-46.2849328080231 +11.1672433480996,6.54403623498673,-45.6683923480982 +11.2763702216517,6.48479717072366,-46.1672919081496 +11.3722767418067,6.45670408768819,-45.1123093748963 +11.4647506128248,6.41103145109497,-45.3862845858787 +11.3327584997777,6.48541539401783,-45.9011981180487 +11.6130010238503,6.19200760672429,-46.6192569672756 +11.5459380264435,6.285371114707,-45.622189753402 +11.6944538010556,5.99576597330534,-47.6017373292823 +11.8076989052734,5.78063437788316,-49.9542663325838 +11.7593647845575,5.80746456385306,-48.536792611645 +10.4728128676854,5.76833058736293,-45.4215133733309 +10.4816284846902,5.77534939746344,-45.9007247030024 +10.4887828446898,5.80868828498817,-45.7829324062521 +10.5028820707474,5.89184270207392,-45.7562038108614 +10.5004206824152,6.12119669173941,-45.0529648386135 +10.488899503764,5.93227096758087,-45.1962583642315 +10.4952374247104,5.95368808877715,-45.4661091730835 +10.4768063353289,5.88847788246044,-46.9571294423515 +10.4371467360009,5.78477288663422,-48.9420006635376 +10.3622839639816,5.41109002405435,-48.15214339278 +10.3303841406791,5.42489938361413,-49.2576250424056 +10.4130929810613,5.34855892372133,-47.0623580682979 +10.4253094152741,5.37970904998061,-45.3631618345173 +10.3790617819449,5.43526249530174,-46.9552079410193 +10.8274431277466,5.9219798621444,-45.7252291834432 +10.8771397782075,5.9529203391504,-48.3271566678006 +10.8718248518529,5.97344942498271,-48.0711689385719 +10.8451007763602,5.99262103252167,-49.7759530365614 +10.7274787537328,5.91046245732643,-47.2948911075299 +10.6945937138797,5.98437226283594,-46.1111465205043 +10.7604101058612,5.91249502914112,-49.2633878776619 +10.6640824226786,6.01045559004985,-45.3754017533234 +10.8126260376894,5.9674009337032,-49.6360241718905 +12.0282397298283,6.67389396365372,45.3326617760266 +10.5864562249852,5.99992015591779,-45.3901357931316 +10.5617262381887,5.93867135410469,-45.5386964314598 +10.5098259286125,5.91014770187086,-46.0504539426624 +10.4592057387464,5.85031673888344,-47.0399994019846 +10.3037026235454,5.92020728044877,-49.7108859544626 +10.383426334642,5.83555597990006,-46.8589100080845 +12.0773931312147,6.64996865726424,47.4370016853275 +9.79481041115977,5.97339225533897,-47.6630255224661 +9.84383305365347,5.95632628264474,-49.8734657567231 +9.82075882845967,5.98099647580169,-48.2320486426985 +9.7853678770033,5.97836091968103,-47.3352005517746 +9.76056997422398,5.7291176917513,-47.118067244572 +9.78014849648191,5.89890605667027,-47.6812231992758 +9.72839822685027,5.52816531009071,-47.3382661631078 +9.69991411037605,5.42143296656791,-47.9109219644602 +9.75087999096706,5.32711971388048,-46.9105134251692 +9.71647283503256,5.37897828195737,-47.7654514458171 +9.76640167365554,5.40598410909396,-47.0778232079656 +9.83395802112931,5.43425051619385,-47.296424329935 +9.73197966031512,5.35307121595403,-46.9499240001344 +9.79665277031559,5.48811888128751,-47.7415430492303 +9.88139559737279,5.3904525557526,-46.9363541718857 +9.91751041972465,5.43378886406135,-45.3365949033714 +9.94671793855841,5.43599555749269,-45.9041878190093 +10.0504679228938,5.45971914831509,-48.6634203083393 +9.9911938739587,5.44215298324748,-46.2585343431028 +10.0392349775131,5.416610963044,-47.4098133395614 +9.67031903343796,5.74145766625781,-48.2336893161369 +9.68343590822719,5.76197523343753,-46.4774163538447 +9.51242265615358,5.96726313219217,-45.3885709731198 +9.49646066287584,5.85611671329702,-47.5868854508344 +9.3491095369152,5.55123568418612,-49.8496897283783 +9.36775877953612,5.52856978596734,-48.9506771072045 +9.35131423233339,5.49271824233735,-49.6265754290633 +9.9660385519208,5.57542636267937,-49.4360729317837 +10.2255631969869,5.70562296323322,-49.248275340798 +10.3209684415958,5.75932212631342,-48.8952152473951 +10.0985700190746,5.65788605144778,-49.5429302552968 +10.3625585919385,5.8969535784023,-47.8032834973417 +10.3928514004969,5.93055165963597,-46.3019637730914 +11.4501131653921,5.83190240006194,-45.4355966625682 +11.3832462292382,5.72961571565863,-48.5686091251602 +11.2520229873614,6.67295929815531,-49.7398956423786 +11.2910550138046,6.68781244253215,-48.9129123414453 +11.3362499190121,6.70374832509281,-48.6979925013207 +11.3837982222481,6.73814691949504,-49.5570300809167 +11.441131768805,7.05226680699407,-45.0854242027683 +11.6200570023498,6.82952485560453,-45.0521685477926 +11.653836704632,6.70896506624334,-47.7996580418191 +11.664105840419,6.47559441823158,-48.88280093668 +11.6528635136051,6.55014691595888,-49.9324718473686 +11.6693583954313,6.69077541689551,-48.7588215137398 +11.6664126239533,6.76655930714429,-49.3088123912189 +11.3595724521542,7.2130886525406,-47.5892134329995 +9.77674600297726,5.31269758811513,-45.8508457236947 +9.72488107980366,5.38709291154078,-46.9034945115432 +9.65787983171772,5.37436551411869,-49.0039199585747 +9.39389621508127,5.7174643193934,-48.1657848667492 +9.48175490273963,5.68301018575203,-49.1252419481114 +9.44212070386445,5.75712243659057,-48.3670410387215 +11.4369100088847,6.28813970385006,-48.4733249441501 +11.400535162772,6.26702479277975,-47.7532341017488 +11.5110250631425,6.47093471450121,-49.3785400153438 +11.1707068955839,6.63603445513199,-46.693791005792 +11.0877392199324,6.78222818778297,-46.6112221976334 +11.25936000151,6.42052579050208,-46.1693828692401 +11.3441650695904,6.39676927949251,-47.192522291672 +11.0629884127985,6.98435465229819,-47.9278661334174 +11.0876721997378,7.16789721135791,-47.6879850173578 +11.0814708462744,7.1855688298197,-47.253534023227 +11.0739547353044,7.15171688911522,-46.9059981459683 +11.058995717449,6.98940746249495,-47.8048848746606 +11.0586301908412,6.95329576312205,-47.5581145395829 +9.34800118691079,5.73268182069752,-49.1388663505152 +9.27323991115176,5.70203436335268,-49.9864676085136 +14.2338280573277,6.40923642273326,48.8424401989272 +14.2019828845499,6.64034866145752,49.6163388013918 +14.5420107574874,6.09090932211617,45.4305563130127 +11.0266666807216,7.13298731420014,-45.633297396028 +11.0484007766915,6.67816004576815,-48.8045465343841 +11.0375121653809,6.69774157051296,-47.2157049467436 +11.2886366037198,6.35647901571886,-49.858660525581 +14.6030903048349,5.97445296276069,46.9245107953444 +14.6486010788437,5.8022444755015,46.2128803409669 +14.6235971152163,5.87566141204297,46.0170443437177 +15.0035789260116,5.64676663148039,46.2634838807944 +15.0692795636063,5.68555571482725,46.3563870736501 +15.0834884040425,5.74772668678958,46.0489964075402 +13.9661678947459,6.34694221316414,47.6432322314712 +14.0491186547225,6.32361400142131,46.3003738912772 +13.9166459056327,6.33279070982224,48.8779429518619 +13.8963767475666,6.26682236334431,47.8159802888998 +13.8861810955261,6.26467020171212,49.0540204980199 +13.9112735698961,6.20655595474331,46.8355092497688 +14.0001890250077,6.2221613768834,45.1266298802505 +14.022072663457,6.26126064852153,49.6611042117952 +14.0251553027746,6.18756930031951,49.3827767630209 +14.1628276336705,5.94893959148124,48.1854477214031 +14.1299601095205,6.04152370172422,47.4216277149341 +14.145884862394,5.97798800953386,47.7079852816374 +14.1601139849266,6.03869718784002,48.016398185598 +14.1740360528521,6.15684106790289,47.8487862520095 +14.1954507156884,6.21992848306318,48.0381605972624 +14.2943916395278,6.27264924839639,47.3522579488497 +14.1774738166805,6.21174142666127,47.8016212599401 +14.2306283007932,6.27962373489605,47.7924417735307 +14.344185480216,6.21900660630996,48.0628452888394 +14.3925401523679,6.21828257137974,49.4764210207013 +11.4354000066577,5.39072634140739,45.3002336968896 +11.4081972508649,5.51182572152379,45.1112630723786 +11.4005772660352,5.66371817732256,45.3954958815832 +11.4082316858143,5.70249185463851,45.0253707662242 +14.8041700569607,6.37923461078793,48.8227902293882 +14.8999755322152,6.31594192130021,47.6604648110623 +11.3192821706396,5.86624532808137,47.3483445464374 +11.3131717124287,5.96152351212614,45.7339609052549 +11.2909385543,5.66635415419438,47.5177445833934 +11.2923718004095,5.72338554580109,48.2030508948764 +11.309208449539,5.742360646105,48.4519933250839 +11.2951630422719,5.64719321382145,47.9526290356594 +11.3778577226822,5.98868840209598,49.0954280695523 +11.3452718282753,5.8731527116017,48.5514059972806 +11.3882056670422,6.1165912690462,48.043000341362 +10.3238582232045,5.99880616733074,45.7983767026538 +10.3160948993677,6.11482507266452,47.8805386851396 +11.40191758365,6.10886834890569,47.5054261774283 +11.4532885382999,6.13386649010133,46.362707818483 +11.4202775887575,6.11197936078819,47.5787235051801 +11.4799038126623,6.15142583618066,48.3022492089486 +11.4648677243512,6.22934262496664,47.6322140613765 +14.992560608708,6.22682267238419,47.3205856895889 +15.050004860632,6.09138846986536,48.3663689599167 +15.1123655920185,6.03597126062003,49.8565015248004 +14.9820068528484,5.62820218057519,49.8479805804426 +15.000681009951,5.59578697305739,49.9366096085722 +15.0086811351717,5.70957239522682,48.3411498603296 +14.9673312357217,5.77590173648445,47.7805809121156 +14.9983150256046,5.61489559855995,49.2426424943178 +14.9042353815356,5.8300525457641,46.504275515536 +14.8251727488829,5.8871604117547,45.5284356967837 +10.128284941436,6.1133454817987,45.9296706150288 +10.2787246914031,6.14075371627147,47.8732831468628 +10.2042868108418,6.12672459069396,47.5035842538671 +9.84650962116993,5.91755749007998,45.085293893736 +9.90720968025359,5.79635465279769,46.8548560203854 +9.95676326328821,5.62982439470022,48.9958710771421 +10.0348059208184,5.54063532654803,49.7619932360537 +14.6343911314849,5.96447569233228,49.8049244567215 +14.6474089356169,6.04972635001765,49.1766043848313 +14.6153450456015,5.92224985847771,48.0157722552778 +14.5905089019118,5.8928496800716,48.648803842398 +14.6034065711138,5.9514964908543,46.4524483623572 +14.1284940108564,6.10663768845812,-49.8171280424082 +14.1590801827904,6.13387844458267,-49.6787896729317 +14.2151751080882,6.19776717321081,-49.4122620614085 +14.2859012013806,6.16675908755253,-48.6088001858801 +14.334863104085,6.10574505206735,-48.1414230507364 +14.3755385171879,6.10660585758328,-47.1228180596755 +15.481378605417,6.32268581035614,46.0483656376775 +15.5593499063108,6.40818949161904,46.0089986074169 +15.7276857879787,6.47686721429773,49.0265069183844 +15.7760893957894,6.46381798004548,49.9274350857537 +15.9312618949722,6.40535242099467,48.4526543015923 +16.0175891824279,6.36536010312732,45.9404346296986 +15.6630139627888,6.45384175209653,48.1994838087857 +16.2779295299359,6.17516647675079,45.4891325263047 +7.95222154782546,8.0561920604408,-45.0515706592985 +7.90134171671047,8.0673076492731,-45.0155867335804 +7.82444251403499,8.1967970025646,-45.7110559995166 +7.87785460639829,8.44165396957425,-47.1293041112591 +7.84957677748827,8.09553222237549,-45.2528970407501 +7.84690081739538,8.26760495333949,-45.8281436411934 +7.93305545215299,8.77215055390151,-49.016184976763 +7.90901688135908,8.60220274118408,-48.0987702301451 +15.0728360381243,6.87316743232657,-45.434353760615 +15.0535495826469,6.8135168161509,-47.1606784237849 +14.9994884573675,6.71199370472965,-48.8453503309946 +14.9707793988801,6.6253825047491,-48.7113690867876 +14.9450805300378,6.61607203616434,-48.6363653522053 +14.9648860723169,6.62884622992719,-46.383035500462 +15.0025492237453,6.70408904798115,-45.0956523278914 +15.0746528954987,6.73776437320574,-45.3841919654973 +14.9506422241257,6.58489984277777,-48.0465017498422 +15.0087913152164,6.61891678132349,-45.5680545237561 +14.9843684656995,6.5463846892997,-45.7569056855738 +14.8911552703625,6.3895835528371,-45.2799597082749 +14.910177511202,6.33736622811443,-45.6411445278037 +15.064452416068,6.3685399115171,-48.0721921901988 +14.9464015814446,6.3807507086207,-47.007728202085 +15.1078751912828,6.38192901547538,-49.5791435320433 +14.6678446170285,7.68766564227866,-48.272668043763 +14.7422469269664,7.63968000820011,-49.7062619868829 +14.5222952567965,7.75465657018474,-45.3449444714718 +14.6041546577539,7.70086051388686,-46.6654143319013 +13.3076587737196,7.13948032735156,-45.4988893500765 +13.2807251554867,7.002898158357,-48.1108313940722 +13.2807417811458,7.1170511047271,-47.3292581456837 +13.2969775016026,7.14517972977507,-46.2916970086042 +13.2771540552953,6.83345297529831,-48.2444870022732 +13.2748245675403,6.93946995666722,-48.3392422439749 +13.285951550217,6.81963240314402,-48.8354916880039 +13.2992218042981,6.79559311340207,-48.1777983772433 +13.2714627122725,6.88413423701518,-46.9945846354801 +13.2479306972635,6.81332172201877,-45.5452675364917 +13.2692728629007,6.87947497220946,-47.0639238038692 +13.053433214735,6.08293062674787,-49.0944596023031 +13.0656390572793,6.0688013290353,-45.0378726349095 +12.8970651102382,7.02834413634718,-49.4945027105227 +12.9522102127699,7.04474368335353,-49.3202001287646 +12.4746955749875,6.6239492842512,45.5189606243954 +12.4646585588048,6.52126390841972,47.7510160510042 +12.4614540238841,6.28278461731997,49.3221710762091 +12.5237960611255,6.1705114495781,47.5538769055134 +12.5888272473932,6.24535662602168,47.4768258353643 +12.658211232573,6.3824855734457,48.056396930835 +12.4406941267226,6.38337326371418,48.8538411077701 +12.7151627097524,6.41945569130346,48.873780344528 +13.428134861312,5.74805309163471,48.0544992801596 +13.4961864381438,5.73149241586365,46.7711104577424 +13.5738418044512,5.74405557133316,45.7577195159449 +13.6125974925587,6.06191009769173,46.587324654652 +13.6267636979883,5.89260851933289,45.9764323010727 +13.5837925251208,6.21833467625632,46.8957153738559 +12.6017480703613,7.35171171281117,-49.4120233704329 +12.7715422392717,7.12061050317197,48.225705030858 +12.8198621173431,7.08416513216448,48.1737918604556 +12.7979600561819,7.10891936744038,48.1774142265911 +13.0064240918673,7.00697530852851,46.5236836890153 +12.8811497457643,7.03613776262079,48.4027931643157 +12.9512448892793,6.97527160507893,47.9456807838391 +13.0334482861311,6.96350024645172,45.6798402866795 +13.5167377559214,6.52700618634293,46.7791905311524 +13.5165586829501,6.59630115645224,46.4219773878153 +13.4943322177066,6.56692025521832,45.3132838978924 +12.6908703507755,5.72035056455575,46.2761436123728 +12.9586924267771,5.77986531351737,49.9849078026253 +12.7337864473668,5.66991818755385,48.2108234117828 +14.0334740381915,7.08129023867552,48.6271246539621 +13.9625923412391,7.23298234815769,46.4633732503601 +14.3312347017722,6.69067157517329,49.6275245985721 +14.3759150470237,6.75714987161546,49.1338534678933 +14.4063350450368,6.73676326084554,48.431938800852 +14.4518060995537,6.66458209322712,49.4185954735445 +14.4828895879845,6.541894636175,48.9698555446454 +14.5721736140189,6.31798986387708,48.1338383774164 +14.593823161938,6.29513354001656,48.9148491463942 +14.5608220841745,6.71773212842743,46.9712197089453 +14.5679396968824,6.77732195715679,45.9404817920003 +14.5737898333481,6.58195552051633,46.8179604947018 +14.6054953107341,6.44010181995126,46.7955231686068 +14.5494842818713,6.08306612968267,46.240921664049 +16.0061908420427,7.55184337138966,49.4510927628326 +16.0113071131801,7.43967528350775,48.1544248294339 +16.0155331391368,7.50337431032545,49.4348258522944 +15.9940492913686,7.43772499294336,47.0095021605097 +16.0079186313014,7.38011880511647,47.7777484383464 +15.9976909506281,7.21599172295524,48.2190355160882 +15.811705606031,6.81445876838583,48.7266002328131 +15.8651035023726,6.96691808161337,48.3090930018508 +15.7018784685673,6.70008443555061,48.2167641272902 +15.9582332288383,7.12637999423727,47.9493196806488 +14.5063059562426,5.8509978901993,49.5573799516422 +14.5249641183519,5.99727511434606,47.4717771206076 +15.0065615628076,5.90897974140457,48.6606546645887 +15.0612359152265,6.06074752505735,45.7401007576626 +15.9663467184075,6.44883973377102,45.437816344916 +15.9335970397407,6.54467480777877,45.2510212586234 +15.607952248819,6.73103992557542,47.3437536489461 +15.5041049360263,6.71639140805708,46.496282825109 +15.462651928951,6.82554910375828,46.1807336854872 +15.0482006407011,6.37739342950805,46.3360679880627 +15.4017951143948,5.9497400913838,45.3005751338309 +15.3535662452717,6.01632773741143,45.9978719944481 +15.282232481302,6.06385605376357,46.380582758422 +15.1902848057353,6.17646642828613,45.2547732877217 +15.0081034358369,6.36956617732848,45.3648544336669 +14.9386373114807,6.36373417386156,45.6741600265766 +14.8860123083824,6.30762712369853,47.663685299551 +14.8128957764263,6.1716172957218,48.7802126181954 +14.8432910218964,6.25276297043251,48.5215453159216 +14.7810334861917,6.12107307228522,48.6568814048319 +14.7687023718158,6.17310238721431,46.9860455827819 +14.4232224287488,6.09793494075067,45.9798264865246 +14.4517780426215,6.15437903700744,47.4759151112666 +14.4892548462601,6.21836324194829,49.0099662624458 +13.9413731012056,5.74135214717124,49.0254689797954 +13.914943514921,5.86533390560217,48.4709945961695 +13.9705805869476,5.72348592001252,48.7264361835045 +13.9977549556907,5.80805811036637,49.0284511844589 +13.8614159065292,6.08913545852636,47.8715559035144 +13.8077984777323,6.24083225028803,48.3667950671008 +13.8895862862829,5.96408475863158,47.8635055909137 +13.7726054314769,6.26838381114681,48.5378074044609 +13.78005790227,6.29544195087393,48.3492866944481 +13.8636947217418,6.20694389851649,48.5358117216819 +13.805037887774,6.2219049480952,49.1562096623215 +13.8908147183562,6.28319776256439,49.1635871803541 +13.8627277872209,6.24558410810356,46.9822954880463 +13.8677528646064,6.30306550123247,47.5120648325899 +13.8461815235385,6.24066543516439,46.0086827407623 +13.8181385515514,6.1654280251397,45.0142814974301 +13.7917947715523,6.14654887148824,45.3532700611265 +13.7816247902536,6.16803272686455,45.6166346944722 +13.809591954358,6.35897883146707,48.9155379661804 +13.8111101371643,6.24618030528906,47.9057192624295 +13.882568946663,6.39162306374841,49.430677184515 +13.8766024272656,6.35210301874178,46.9197487015251 +14.3987138036807,7.14722597028917,-45.0950803145302 +14.4390990966217,7.42462359620481,-45.655641992365 +14.3970107216951,7.52468975273669,-45.7586417688009 +14.330069075586,7.72109020216742,-45.7488857818433 +14.2708955405194,7.89997161672415,-46.0511841037772 +14.2153514818835,7.96486978759989,-46.095241419598 +14.1641618912,8.14217580411712,-46.7510111451253 +14.1344544537465,8.10793772439135,-49.0160536322476 +14.1601653649518,8.07542520476627,-47.4833277722882 +13.0805618730069,6.13445254918123,-46.9868405848524 +13.0512317020145,6.25041646303292,-49.1976181192024 +13.2637965665114,6.10887878095532,-47.4059093735691 +13.3830485526947,5.97336158347947,-45.5037740993579 +13.8355369360898,5.65051967105463,-45.8807349397312 +13.1743437405581,6.25014754194587,-49.9004990098151 +13.9378866954526,5.77955828978316,-48.7150922575899 +13.8776427708552,5.7452270867775,-47.161628482091 +13.933207300226,6.09192153541803,-48.5555230058456 +13.8528153105109,6.2007943344444,-47.4157597109706 +13.7911483620247,6.27408857987534,-46.6372178252372 +13.7561193118811,6.25242057813223,-47.2342964847517 +13.7142264744013,6.32810435323505,-47.200512959949 +13.6807264377594,6.30915536806685,-46.8930525348241 +13.6702676751542,6.24979164548736,-47.3506896170913 +13.6401100632924,6.16203453577775,-47.8233738434731 +13.5567142270626,6.08424263612113,-49.0043274605506 +13.4890972648578,6.09427162966184,-49.5019852928914 +13.2344596132019,6.30277799952046,-48.4504432360926 +13.2401904041615,6.42783475104179,-47.3271927201672 +13.2229584041889,6.33521127458371,-46.6019083805221 +13.2601617104854,6.38339736488047,-46.8836743964119 +13.2897717951808,6.31026660280576,-48.4870918343261 +13.3271976036054,6.1890708041018,-48.8471529469583 +13.3874397478398,6.05042999504654,-49.8629662514831 +13.4431973673711,6.01864451527957,-49.9059829107408 +13.5328120623611,6.07756411836184,-47.6843319299669 +13.4946409400729,5.98832395784506,-48.8349000294445 +13.5783182408951,6.17145878401163,-46.5359592724483 +13.6016915373938,6.31403585647633,-45.8339475626983 +13.6069219062218,6.47033270061057,-45.7019488287297 +13.6354802391114,6.54093095714731,-45.7526520939637 +13.683193906594,6.71511463936565,-46.3160973276292 +13.7308256878408,6.72063547652041,-46.8188430195892 +13.7553452332448,6.64748852997757,-47.9319079301346 +13.7770117705911,6.66451449935242,-46.0469523495128 +13.7945503197074,6.55528448016975,-45.9761722068088 +13.8145676537921,6.40392057699686,-46.2045992351707 +13.8500108815082,6.31211147605611,49.0783007953752 +13.8632513772237,6.36199850439551,47.1312378914313 +13.8595608602335,6.25981870265828,46.8968070876857 +13.8393162082393,6.17869555902554,45.2230590585409 +13.8419530265875,6.09342157978284,45.3643785964299 +13.8481398976734,6.09473338953892,45.8223101596852 +13.8489629151743,6.03835642751912,45.9416799818069 +13.8457259525767,6.00822515630377,48.0602654039623 +13.9406541332435,6.10182630707038,48.8934409254976 +13.9615493910518,6.19531342557902,48.2416379772661 +13.9740025219556,6.275063416813,46.6531022117024 +13.8971856831502,6.08839542727535,47.0260389594759 +13.8437098484034,6.58772030129064,45.1808742316298 +13.7944921379231,6.58495900800234,45.7018672382137 +13.7637643246623,6.66158787753845,45.3095097566381 +13.8948480072497,6.29811076217181,46.0517984039176 +13.9667336454003,6.17332457193514,48.8349488184984 +12.9454344326822,6.01554278023526,-46.5550872427316 +12.8781284194357,6.11936684604346,-46.7144355164169 +12.7919849940945,6.23881167253897,-47.6446591377055 +12.6728093679911,6.19745831437367,-48.1659673469844 +12.622833045247,6.13796413071185,-47.3860455782264 +12.7130251726503,6.20198165188976,-48.2162115438059 +12.8433052647737,6.24106693814137,-46.6994246990593 +12.5693293381966,6.04513513840076,-47.1206682437547 +12.5165268394379,5.93701064893476,-46.9251463652069 +12.4452071214463,5.69900212527111,-47.980915758289 +12.4170325632309,5.63365556785812,-47.7716273415638 +12.4737754794732,5.85465090024933,-47.188935347298 +12.3727265460837,5.53081725327882,-49.1114536552583 +12.3351590359411,5.58526909707434,-48.7786280207874 +12.223100289035,5.6358285777778,-48.2856224648467 +12.2962806790919,5.59235270609892,-48.6711473736957 +12.2497114387892,5.59844911069504,-48.3963319819088 +12.2391158374887,5.62893942996735,-48.1773721311534 +12.3149649489953,5.8241773626836,-47.2459697908278 +12.3603217988642,5.84310632340113,-47.7176574243276 +12.261783090584,5.78763657502885,-47.4823898800265 +12.2340282806263,5.7703574508315,-47.7421665218128 +12.395116373337,5.73944650977623,-47.3372575668118 +12.4289887786265,5.77553183455313,-47.6048750662575 +12.4578340029705,5.73778813295169,-47.1724781460855 +12.513076679009,5.76129915308411,-47.1994137344307 +12.5843497968955,5.82879697040698,-45.8921666889865 +12.6549050171498,5.8411978392332,-46.4660650442396 +12.7391093882027,5.93163112993469,-45.6349868831352 +12.8662016556681,5.95853179172123,-45.7772371288708 +12.8024177541785,5.93406772585973,-45.6275598564911 +12.9437383078572,5.96813840009242,-46.3287358774672 +13.0002138243397,5.99213923004164,-46.9987838065053 +13.1480087403952,6.196644273364,-47.7102670957567 +13.0695201172731,6.09018913000876,-46.9924880889071 +13.2588851855083,6.37999824984282,-48.6513091600469 +13.2282672292587,6.33687426473131,-48.9441319243757 +13.2492508428684,6.58208214933969,-48.877060744988 +13.2310342473948,6.64651908945484,-48.1222628781136 +13.2648506407818,6.42723875237642,-49.0040646628491 +13.2605658003796,6.74513129284634,-47.0137170455179 +13.2563081349362,6.93493630943254,-47.3887462484086 +13.1721295134804,7.32936786836196,-45.2488494355694 +13.0518208302369,7.28990060332162,-45.915413090075 +13.2478838662044,7.07424171655358,-45.6262388289535 +13.1199143121939,7.37458114241929,-45.0217813293314 +12.9143122719197,7.00928208395437,-47.1424393391315 +12.8681694778252,6.83800524947218,-48.4006882096419 +12.7934730635572,6.86220523831958,-48.9511869195024 +12.6302252787193,7.11318262654777,-49.8174131835933 +12.558969773481,7.54482842497604,-49.5530098338365 +12.5892098004494,7.8465323172771,-49.4999089857546 +12.9767780383769,7.13483889409438,-46.497050625143 +12.5872005457258,8.20320089864522,-49.7038687714793 +12.51596803627,7.90603993462486,-49.1284067501907 +12.5297113122627,7.74680106656018,-49.5203157063199 +12.5216359878634,7.73621384311679,-49.315480620207 +12.5057410807971,7.81634508223297,-49.4935792496103 +12.5239382289104,7.70054347651447,-49.2809173918192 +12.6247623238589,8.00661623945115,-49.7617579029098 +12.624059468161,8.27979339159645,-49.6318902997136 +12.6253166688216,8.19011863854654,-49.7409326870101 +12.3514904593844,7.48519557000806,-48.827724821884 +12.3272114776854,7.59345657878228,-49.2601925788861 +12.3319157220477,7.70529656255496,-48.9333937764501 +12.3208918493887,7.77821297529427,-47.465252745014 +12.3132445642946,7.86934097182819,-46.9511183958458 +12.3238601935779,7.86273057901727,-46.7475287192639 +12.3303636180299,7.94602343573709,-46.7989822107965 +12.3303226746972,7.93903998374511,-46.5093488840849 +12.3214710499894,7.93319615501883,-48.0859229758131 +12.3796597988305,7.92947415387585,-49.1685514648969 +12.3418807301213,7.95906913362079,-48.8882721600638 +12.6159520884195,7.82319045529634,-49.3567976302914 +12.8348699479136,7.93116625892554,-49.1903144245537 +12.7265808769982,7.89536861339689,-49.0928136653182 +12.9166798530358,8.01460200104007,-49.3970999049587 +12.0298848762295,6.91442986875521,-46.50836206337 +11.9718470787461,6.86430275746577,-46.9299399134526 +11.9384285736682,6.67827001976985,-46.8574601758714 +11.8907822345123,6.54110225847746,-45.6746860755346 +11.8637639247779,6.3955307723673,-45.1808920841115 +11.8035755339284,6.96926193833292,-47.2489520911523 +11.7882957983265,6.96101698581755,-48.8615718497681 +11.5624934922417,6.38680214309694,-49.4212545284391 +11.5900124177076,6.5226351257383,-48.1242063853012 +8.64680860712718,5.7887694864216,48.4187942801877 +8.59389791686062,6.06238864819958,47.3781815695999 +8.61302277136389,5.88412958271779,46.7433704982209 +8.58940889328616,6.19580903357896,49.1010694376382 +8.52637599883244,6.34937601519672,49.1074741844978 +8.53967829947985,6.24384294955369,46.5584887170385 +8.52257924484176,6.28507439574377,47.7109358784662 +11.5539420547333,6.22058266713593,-49.7700654180836 +11.5571975975106,6.17374614948499,-49.8621184158304 +11.4782418534061,6.2199161091909,-48.4521598502577 +11.556522742714,6.14201228299387,-49.2165520510485 +11.5361367140003,6.15879642022534,-48.8439542427927 +11.4532724134086,6.29979153761588,-47.6605985342232 +11.4380343393446,6.41543298009573,-47.3867344624102 +11.4384437309851,6.35447351456981,-45.308750461931 +11.419525375339,6.22451976742356,-45.6514706900825 +11.4108410164464,5.95214998670107,-45.5163899165195 +11.4060129332567,6.08551699605116,-45.5093267399546 +11.5102201478034,5.72639572001409,-48.6500295432734 +11.4520612977346,5.78994561293435,-46.4136780759302 +11.6963833980774,5.17653569679175,-48.1489928091 +11.6545522365429,5.26643603845223,-49.2715266563033 +11.769013588201,4.94119254129167,-47.2743165277072 +11.7268916718472,5.05314845913036,-47.6191704361998 +11.8255368999954,4.85413425767511,-47.5484541184211 +11.9792008474271,4.72621311575413,-47.6236656470138 +12.0627839708305,4.68718547331164,-46.9802348652035 +12.1284873357342,4.74488186611642,-45.2595036867051 +11.8912814691628,4.75977922591643,-48.8368064660342 +12.3039244612312,5.00698603491708,-45.1162545017991 +12.3294158417946,4.93267806460992,-46.5294506756535 +12.3095918408904,4.93943406359984,-45.5757097719952 +12.3716184284975,5.09340311425596,-47.9273371899754 +12.325961250129,4.93867100361368,-45.3722008802152 +12.338215562716,5.04914980540965,-46.6756050229033 +14.2160935838804,5.53492337388922,-47.0311816269588 +14.2379213498,5.47995072443406,-46.9096782397031 +14.1932503944322,5.5623991132385,-48.9399781256146 +14.168782512396,5.72102216689833,-49.940206554404 +14.2361777128389,5.54898922741658,-47.7615462976033 +14.1898994998315,5.49686036447077,-47.6132797849297 +14.1631795068664,5.47175901016559,-46.8115264827923 +14.2179657093387,5.54199233698577,-47.593765580978 +14.1141803713226,5.4054628595512,-46.2235212404602 +14.1334998735572,5.47569639183414,-46.3205016274643 +14.0774662499994,5.42163124390111,-46.2073497595204 +14.0136585644361,5.60553908783603,-45.4293548074152 +14.0520317656289,5.49730229275898,-45.8570210182066 +13.9777994012644,5.65557362363861,-45.1334481835269 +13.3683453644733,5.25066808515135,-46.8621680487508 +13.4492400500975,5.30638393129121,-45.3503759317057 +13.2923438506426,5.1859812538478,-48.7510539237478 +12.0127762692397,6.56103335026899,45.1458383994804 +12.0755783905484,6.52660296758977,45.9634897165552 +12.1343517304433,6.39961412472443,46.3323353525822 +12.1878999335723,6.25190665946137,47.5207864037758 +12.2363254537519,6.03311527072107,48.5359113343953 +9.75299260081154,5.70947513320886,-45.9897600149955 +12.3720905804748,5.3662251503741,48.805320504911 +12.3978229315096,5.30208983494529,48.6754006589767 +12.4033781840684,5.26360327528002,48.6822476582445 +12.3210236927737,5.50206067086577,48.0097178210722 +12.412825111211,5.25681709117724,48.0048536071272 +12.407038547582,5.08627168537404,47.6334667285598 +12.3829716046295,4.97565618663324,47.3916310677681 +12.3847258671214,4.86034165856098,46.5140927271754 +12.4069472911079,5.18377504110927,47.285635272076 +12.3838008065961,4.74687430380634,46.4198616600691 +12.3890485226671,4.69290185618914,47.0008863441505 +12.438413019001,4.64737848855802,47.1474035447134 +12.4707142251089,4.74559617407603,46.5322759154382 +12.5061741295363,4.83235103349276,46.9228505222354 +12.5494062795436,4.95595708308687,47.4393067283071 +12.5540834581865,5.0121202468956,49.5296864383432 +12.5291178409882,4.91576923808679,48.0737767866368 +12.739474586099,4.91281480633089,47.5958913502518 +12.7443034642304,4.9399080387663,49.4810313496798 +12.7451985603218,5.01044293781832,47.896041147602 +12.7536416074642,5.11735073747844,46.9183858431142 +12.7415613914316,5.25800281556204,47.8897039921547 +12.7180951795126,5.41196775626032,47.8474818707197 +12.6113669500915,5.51056163019144,49.3495142870802 +12.6746616415725,5.52809774015331,49.054503736989 +12.1542237040698,5.13288955797794,48.3854904496619 +11.9268070043124,5.24787758196782,45.2964267945663 +12.0442902206316,5.16820315892366,46.3062739546352 +11.4821555610729,4.9471589305304,46.5418173625952 +16.3786472952652,6.61137817907523,46.409812184704 +16.3798050126198,6.59271093674357,45.3816565885124 +16.4224602751615,6.77246622925165,46.375933755574 +16.4033329786379,6.68244835432984,46.827668306331 +16.3610299911808,6.89564687514062,45.1108371086558 +16.3983468700271,6.85479141460962,46.2197292770533 +11.4103617154845,4.92600962348485,47.3206418445055 +11.3344431899816,4.93499146291377,48.0571344789977 +11.1455677131302,4.89791672775321,48.5190006617232 +11.0706162292056,4.89005291899713,47.7033177492813 +11.2420272034557,4.88940869475274,47.931763676377 +11.0505790116874,4.92330138446753,46.1733281103202 +11.4592796271283,5.24480624354485,47.3922916329354 +11.4427964940573,5.31829082556053,48.3523824146591 +11.3471735545276,5.36624875176502,48.2446337626745 +11.3287441795498,5.31270725256997,47.0044134683734 +11.4639460392368,5.31956339913242,45.2732960271362 +11.0004502712834,5.29229171025692,45.4331565285315 +11.0154909342975,5.3670648727668,45.0308064540335 +15.5306627923679,6.1290240030595,-46.8743338323124 +15.5507366822409,6.14913418119846,-45.31022371408 +15.4514933005266,6.18484998494129,-48.3797345595987 +11.0267988463175,5.39521717005474,45.4035420777745 +11.0896665724066,5.42347437577941,45.6360914910002 +11.0910840854549,5.42117989226241,46.0701115562793 +11.0868128216087,5.41307142346268,46.128567753674 +11.0976694781661,5.35619088690533,45.3076015882271 +11.4422216842372,5.50203949697204,-46.2663130781623 +11.2871499168933,5.57936275254639,-48.211291819709 +11.0255887118852,5.78404536551485,-47.8576175319102 +11.1570666633883,5.67457803726926,-47.9943306260345 +11.0733531478423,5.71599189093294,-48.2123401916896 +10.9453814587819,6.0630070549545,-47.6572198599259 +10.9875713969402,5.91698826119716,-48.2822403726802 +10.8862329429042,6.12126587906531,-48.6325452359766 +10.8749061375058,6.21473001999097,-48.8955983837881 +10.882695595174,6.20124382165015,-49.6299009946444 +10.8695308865145,6.18508926699649,-48.5976370006846 +10.9590398302682,6.22042990290547,-49.7499178430059 +10.9919316445167,6.31311968517777,-48.6171757361985 +11.0509402409414,6.45750006634719,-46.3135937995875 +10.9239378904713,6.19467343319562,-49.7764267996654 +11.1106137662021,6.543499906849,-46.831309151252 +11.1644965411945,6.59019910689207,-46.4192944221912 +11.1855640257192,6.54922879289917,-47.2930772292482 +11.2179986562201,6.37512046423867,-48.8504993147785 +11.2057043517572,6.43331955935039,-47.7850266676348 +11.2210274489037,6.24629606261011,-49.9359227564059 +11.2222741306532,5.9463578949304,-47.9964276107705 +11.213492975927,6.00037405768383,-46.446215988551 +11.2311052258373,5.98858098723968,-49.2497203780797 +14.7852504318927,7.89095127588356,-48.7771396213737 +14.7353257299759,7.8946009128346,-46.2930827363073 +11.1504105566835,6.02956365068319,-46.0132658457524 +11.0950473334492,6.09325031772234,-45.8618620605791 +11.0225106752103,6.08970411407012,-46.457980933509 +10.950383586208,6.11968597207626,-47.7364558861205 +11.1856784349474,6.00295272074576,-45.5858534050775 +10.8763226182203,6.14278176321005,-48.4596175353682 +10.7923315010191,6.18423995478889,-48.9984290825547 +10.6306169920012,6.21634851841337,-49.1678396297888 +10.606693608347,6.1356542435346,-49.5874171823699 +10.5245830556525,5.99602173666341,-49.9075199317861 +10.4411539192307,6.02763597180385,-48.3119193000601 +10.4196944969206,6.0479558405199,-47.858713031876 +10.4084424540128,6.06740957541453,-49.4245109458802 +10.4748672410564,6.03169383454932,-49.5203257887756 +10.3693785528126,6.11088673320474,-49.1413254334991 +10.342969839915,6.15106273678499,-49.3147003121807 +10.2824189679355,6.06681051873058,-49.375429413378 +10.2635232692362,6.08450279300773,-49.0452523165254 +10.2437478602291,6.04664936167123,-47.0050718942006 +10.2251378161832,6.00295727617718,-46.6168486124888 +10.2059089500593,5.97196157601709,-46.6647731248982 +10.1690839269455,5.97174446992218,-46.5787510114171 +10.1097660353146,5.88351910067622,-46.561102925551 +10.1308524269303,5.98985761734242,-47.4270818731936 +10.0721683949379,6.00153590413349,-47.0820676509178 +10.0300091476072,6.02143579528201,-47.0953296417416 +10.0975331358462,5.94888781249675,-45.7924579698806 +9.98640934093249,6.16818065881496,-47.0073369442267 +9.97761063476635,6.37815124840152,-48.9765883459226 +9.98352911660971,6.19785821409253,-48.4453579987416 +9.97649429970807,6.37629426596395,-49.539783326573 +9.97783145489929,6.45201269940221,-49.6454394439651 +9.97951247347303,6.54881162939794,-48.1223722006133 +9.9590222192425,6.68492880675701,-49.3396281285539 +9.9807084266298,6.56401896396575,-49.1450438010065 +9.87400479864628,6.80855338353682,-48.8357310773671 +9.92989942115524,6.72621049410386,-49.6035773967042 +9.83345035875006,6.90778162711137,-47.8437414763274 +9.73500996795,7.08259738547494,-47.3051698372537 +9.79792452846309,6.94093279534098,-47.2486791012995 +9.61240490261019,7.26311155540059,-47.6166330489971 +9.55039669873128,7.33709979179921,-48.3441662539865 +9.46600110822388,7.31833425262501,-47.2951387505916 +9.44526085705379,7.24485948014193,-46.909700446128 +9.51868551703511,7.39033578768866,-48.1821395096123 +9.49951655823229,7.40219392875526,-47.8948762161912 +9.41778523306979,7.16249185759334,-46.0126659725606 +9.38909174534475,7.06134466898695,-45.1457550616236 +11.4510112228966,6.19525363630305,-45.9252725692265 +11.4564931382269,6.31555914543671,-46.2960772000584 +11.4602052912483,6.47192839033009,-45.2977039898885 +11.4256862547244,6.51496568916934,-45.1637657319191 +9.3736420486644,6.59809922548999,45.5800001126157 +9.4358050453574,6.6179241541826,45.1830365329473 +13.9222622196076,7.54939343896664,-45.1246969717079 +13.8961525511278,7.55125387463547,-45.8147883455831 +13.8269323053157,7.4699628172492,-45.6693631694376 +13.7479390099058,7.37871085632053,-46.3148827643703 +13.6568356964245,7.13462696266498,-47.9820527441102 +13.5515739756679,7.08461325212769,-48.8936262907054 +13.3883662349023,7.24680821152962,-49.8916212319978 +13.4583135608441,6.92777105308556,-49.9865413262987 +13.4071983131082,7.43771493648797,-46.9239504206907 +14.5563496585721,4.8435694026493,-45.384250594279 +14.4879908898154,4.8160986240345,-45.6732385239333 +14.4294003648701,4.88890135161052,-46.5954698155823 +14.4746372041978,5.06959101980941,-45.815098794315 +14.4292270626333,4.99211606395466,-46.5874017309399 +14.510403465608,5.0653438532899,-45.8964053437223 +14.6262608370212,5.25632558571277,-45.6696481720844 +14.1080534165011,6.54907996163232,-46.167195205713 +14.0273444400856,6.44556292862649,-48.6929957850218 +14.5751733995371,5.26647588580217,-47.0897880922137 +14.5156969334415,5.24553930213034,-48.9828344585522 +14.4967043841626,5.20721623654303,-49.9839284037068 +13.7326903442996,6.98391893261062,-49.3575757252207 +13.7139403443119,7.21677538020315,-47.6321830250959 +13.6823007695844,7.61759491105723,-46.5547712816845 +13.7017010080537,7.39055081261724,-47.0998923397183 +13.3376147177574,7.38198729306837,-48.2170380065672 +14.4306579834849,5.12609340556243,-49.9330438214692 +14.3223038376119,5.31600690454401,-49.4169169927014 +14.3154807385861,5.38680432674908,-46.9350940525883 +14.4301101667152,5.19929547035644,-49.7770335031043 +14.2941885432145,5.36636164463871,-48.8170339565632 +14.3407381196262,5.40009853792594,-46.3650062006044 +14.3535514342443,5.31652322597601,-46.6192277940956 +14.3740973555562,5.29233186274444,-45.4808282550793 +14.4110985648678,5.24923095831973,-47.5329635870182 +14.4082344577367,5.32594435548782,-47.7108526410174 +14.3869810237146,5.42176384873532,-47.9450700638165 +14.3816279151567,5.55591720926067,-48.9678475981173 +14.4046968833949,5.60475467794343,-49.4651077111826 +14.3966480707888,5.53769761640826,-48.2555589131996 +13.0676692108437,7.61469032971001,-45.6183813964367 +13.1467986341458,7.54505673426112,-47.3878008249266 +13.2090060133285,7.49563751449594,-48.4632747631078 +13.4195982986583,6.41879991270626,46.989943381514 +13.3351698502819,6.35783051771468,48.399977786641 +13.2645036594523,6.2555714113916,47.654883619742 +13.4820486095133,6.54732164619579,46.6731100810351 +13.1773069761155,6.26950116203522,46.1193879195476 +13.1497425908346,6.27489451582937,45.1074368269244 +13.5117533631616,6.59429942345395,45.6203696624854 +13.2110426929148,6.22856629069778,47.3882849797066 +14.2752019896424,5.80387041095629,-48.147511608638 +14.3032475176343,5.8282807896139,-46.9149033322791 +14.3031891654051,5.9596950882444,-45.2128731208209 +13.9853452222109,6.27545236751069,-46.1581327024516 +14.0367835672587,6.20481608187577,-45.9976369511126 +13.966608013079,6.37136780190654,-46.8385349483929 +13.9212689786651,6.44348378908142,-46.0841881141513 +13.3932445213853,7.38424665514543,-49.8269260864646 +13.362247522509,7.37970417114813,-47.7828762663938 +13.3594961462424,7.35083136503023,-49.0058467369485 +13.6493661688793,5.98170739999911,-46.4514955008077 +13.6673072146584,6.02324620312186,-48.0819567544272 +13.7042727117324,6.07660605375016,-46.4738919336868 +13.7498200225007,6.46265147740765,-47.6488742880853 +13.7170232980426,6.66993325068613,-47.5884340486585 +13.7697147987324,6.36480909610199,-47.87421927471 +13.7474441065074,6.2482074416978,-47.8308339525902 +13.6926855282912,6.77729919542239,-49.808338418029 +13.6154310539069,6.93317194075731,-47.9759339754171 +13.6509639917274,6.83516457167564,-49.8513091671699 +13.5604354712998,6.97045872881998,-49.5452426939501 +13.5194267929375,7.04248505484234,-48.4488606540135 +13.4471695350114,7.08741964863715,-49.8062255845832 +13.3711755711341,7.06598359353447,-49.5115856435551 +13.2795509038891,7.01367422493353,-47.8652990477508 +13.199514659846,6.82932638947621,-48.788049222148 +13.1294093682179,6.72290322278844,-47.3590564237788 +13.0621113747767,6.61555056523138,-47.064519101108 +13.0014145849157,6.41802094893432,-47.586963688683 +12.9370507940346,6.25893765871003,-46.7780083333564 +12.8493366957692,6.13208164455338,-47.1654799644764 +12.7940855058467,6.07116272915983,-47.8679197443331 +12.7211863476456,6.16281704387038,-49.4615466115102 +12.8080384364379,6.38478895954231,-48.9549213329189 +12.7556677328091,6.27935901105232,-49.3824818978302 +12.8496127249412,6.38867442364361,-47.9126360999869 +12.8650661234457,6.44806597963317,-49.970836004304 +14.6666076998268,6.14928978628833,47.6361365412804 +14.5911469709519,6.16317604766526,47.0054538163521 +14.5239044510753,6.20254714054079,46.9502494730783 +14.4490817031352,6.19117990684786,47.4493339866872 +14.368497958924,6.23013573173339,49.1285900794628 +14.2999955649466,6.35681622866272,48.7892019215976 +13.9106207144246,6.58925011041698,49.6690065394002 +14.0668032808442,6.51427826073636,49.703856905423 +13.6798473177951,6.65206676492575,49.9460451065407 +14.8178360316737,7.32147849194141,47.7926623434832 +14.8230012121299,7.24921785170853,49.9977207207933 +14.7930040931889,7.44929857352391,45.5841258675592 +14.7580023411651,7.47078888911643,45.8977743217503 +14.4584069556902,6.64339773307223,45.4625083427923 +14.4087018517505,6.52195429075206,47.370346243535 +14.4334439127969,6.58485063368502,46.5677086334269 +14.3194403728744,6.51727101234457,48.9689122375209 +14.284919243095,6.44989945222366,48.4821834545416 +14.359766959692,6.52634818395524,48.5071746770866 +14.2049731749,6.28131606399556,47.5761203157991 +14.2640437532176,6.30545984136887,48.3778624292057 +14.1664280601521,6.30214037447241,46.5850175550748 +13.812807739162,6.87755963362694,46.3427389195768 +13.6772645092715,6.39516304215346,49.4784771509671 +13.6667423368021,6.33832292257421,48.4454992503427 +13.6418883280377,6.36579792919337,48.5118888910218 +13.6386578576277,6.45070035909264,48.150797617056 +13.700273479518,6.51679614160518,49.4203502367022 +13.8029531342416,6.52384775111068,49.8303060681816 +13.8787493596163,6.56433946267543,49.1112165099339 +13.9576984625807,6.58684789782299,48.2547206724022 +14.048942949882,6.62300769472316,47.8417480913276 +14.1366065048291,6.65852836455469,48.2063779697667 +13.7725101921242,6.79108645929757,48.0645985746872 +12.4124231084515,7.52961173753213,48.6915565635637 +12.4589908570509,7.6073412383652,47.5767970215726 +12.5174017899884,7.65329760629671,48.1787358038551 +12.4860464262129,7.6413132028235,49.5987178733259 +12.4961920628326,7.55962330265792,49.1232259548325 +12.5407424988663,7.40458736588668,48.425167354759 +12.5293823455945,7.34246022826524,47.398211207744 +12.4867171340592,7.30623140131394,47.1848822297861 +12.4565377511496,7.22737273893591,47.1747944534103 +12.4282353024172,7.19383196167691,46.9302529204192 +12.3825964271862,7.21931828037792,46.4674816067545 +12.3579246902196,7.19804216601263,47.3629138624312 +12.2642923967751,7.61693075405071,48.5112643792041 +14.2869196754902,6.74235075274354,46.0714096192049 +14.3761998246024,6.68806435739974,45.185045185294 +14.4040467633737,6.56801930245277,45.2379594327769 +14.4425906438904,6.51294377439177,45.3312897737321 +14.497889518603,6.53401770827805,46.9633924963856 +14.2178281814524,6.67366858273402,47.4743026742265 +14.5133620231281,6.48405783984425,48.8336568831717 +14.5429284148293,6.48053395443808,48.5827401210363 +14.5112897063672,6.48715308518579,48.8077040147946 +14.4473995632122,6.52586425841403,47.0940151278848 +14.5430823428259,6.45429277316842,47.6175724804643 +14.368170699764,6.57092882767663,46.9704878285505 +14.3185590817019,6.63917207551258,47.4999611431744 +14.328123163159,6.70877541069899,45.1416606901165 +14.2645479291337,6.64189040140646,49.9979348487546 +14.1873439070631,6.71338639281609,48.3459189291932 +14.1574308275799,6.73810871533317,48.6114160197128 +14.1260117196118,6.77241518844609,47.442424426823 +14.1107892753699,6.82213034385859,47.6320983220435 +14.1184306999967,6.9018552068462,47.2557233695163 +14.1429329541574,6.91319454363558,48.3037815473612 +14.1487506170743,6.90714579028672,47.2842509690728 +14.1280586133553,6.90416112893677,48.5339326030626 +14.1109157457041,6.91575396312018,49.114517307153 +14.0888174303129,6.98675438833388,49.6384905688274 +13.9474297317256,7.23000270728383,49.8750701701228 +13.9756835832209,7.36346972163819,49.1961114899964 +14.0083114152219,7.44015007287053,48.5084249974755 +14.0651572672716,7.55263149527704,47.9298170128323 +14.1512547020282,7.63229785887641,46.6957483884353 +12.1947578352028,7.45510747329864,48.2660907518379 +12.2310001587978,7.46060121293032,49.7181127894178 +12.2315596578316,7.54199482563925,48.4300041561594 +12.1940594303543,7.4459668636214,48.333295002124 +14.2226073276821,7.63309436757902,45.3968913411953 +12.7152402357452,6.5320617219877,49.1936089230953 +12.893239374357,6.4364622776608,49.8348961456168 +12.7604600712123,6.49313603695576,49.7586260589327 +12.6311609907958,6.64219972985059,49.6872611655339 +12.6796756644598,6.5575105984676,49.067926937655 +12.1383347077297,6.87458506969534,48.9298233440001 +12.1108305863121,6.87005067772125,47.216803603926 +12.0590736275709,6.84355500118077,46.9961355046466 +12.0181526964235,6.85154288648843,46.7301248893756 +15.0023285844376,7.19295565105574,45.2633102250329 +15.0456753262341,7.11251079841025,45.2784318906356 +15.1081513950545,7.03890459810669,45.5693555840198 +15.2163458610866,6.95048262125331,45.9913352595685 +15.3146139743983,6.87364761572171,45.8776082138703 +15.397084816282,6.92067395167769,47.0187209835777 +15.483041382539,6.95116698823988,47.7415529108183 +15.5429074699327,7.0422308255482,49.1003103897769 +15.4242956023438,6.90570607145535,48.9690266169521 +15.3906051572236,6.94942619677695,48.2316059323238 +15.3265172969527,6.9798243608204,47.5496334849084 +15.2543469550601,7.01255073431272,46.6401803803723 +11.9856023582832,6.84393742073768,48.2062413932649 +11.9541219103613,6.8053477527447,49.2633083563236 +11.5760775859441,6.92334051409329,49.7144011246643 +11.5140573500042,6.98520498762946,49.8632817976909 +11.4888007096459,6.96080965115904,49.2926736969665 +14.43817747947,6.58056660856315,47.8201074549115 +11.438273628638,6.9249036672193,47.6527774457426 +11.3958451539703,6.90461462655695,49.3316951563045 +11.3637523038251,6.9306263710368,49.4494360969478 +11.333832566731,6.91850161252734,49.1598387464431 +11.3491071571851,6.95342291001337,48.5816923659879 +11.4120308643748,7.07115138930213,49.6874147703826 +11.4506481859413,7.17529044068266,49.7240438558824 +11.4949644620548,7.20725068644279,49.6936799671311 +11.5765392535927,7.20112593891931,49.5017393415595 +11.6494198527872,7.17266560186379,48.5725865022166 +11.7088085184913,7.10754820421553,49.7637971563378 +11.7631635798548,7.06786527731087,49.0755519596691 +11.8139381515581,6.98972444895098,48.8136410853452 +11.8574592096129,6.89017243836174,48.0425264472169 +11.9232087614062,6.84413960583298,47.5055966090628 +12.0028220824065,6.78099178394084,47.616722807044 +12.7514977239398,6.81527080805262,49.8376166438114 +12.7041917895633,6.83121143538274,48.4475365310149 +12.6878410038744,6.81996796690886,47.9520563168119 +12.6903384040556,6.72744403208056,48.2080231836437 +12.7003966268673,6.6033238641423,48.7796290462567 +14.6814779721209,5.22621434401849,-45.6482936297194 +14.7744546760303,5.2765730440697,-48.1435437616267 +15.4610033168201,5.99829808640704,-49.873336015786 +15.5241462636945,6.04579543932536,-48.665591183312 +15.6350666635533,6.14333054385063,-47.6255372217086 +15.7311170670718,6.29493193718841,-47.299774566401 +15.8276640286172,6.44324324420738,-47.0767133920476 +15.955675749997,6.55998633384886,-46.7651072329975 +16.2338361043331,6.49301606640323,-47.18889644828 +16.0914178907292,6.52067683897567,-46.6789302661605 +16.3640748030114,6.36027521009925,-46.0155647476627 +16.3693505246867,6.3139397369587,-45.3895678585543 +16.3344926387468,6.48979923657158,-46.4302672594981 +16.4300395683077,6.26263954013946,-46.235558391629 +16.3921790491574,6.25558163783447,-45.6642498572975 +16.4456996975564,6.33085162484174,-47.1604178273065 +16.4962208860611,6.30815142076578,-47.8167329855275 +16.5923180384834,6.41078423679353,-47.8685161560335 +16.7225536547082,6.4391307548642,-47.7575281849222 +16.8275493028032,6.44849368121332,-46.609747346944 +16.8807695560343,6.51451730890503,-45.8129266162066 +12.7040638183911,6.45486187931404,49.4329942387462 +13.7312691739579,7.54875087980387,49.9157360192267 +16.0785732803701,6.45553491496314,-45.7842611157558 +15.9985954946947,6.37692901315352,-46.4912291471387 +15.9122848642673,6.3836989831989,-46.7014255196853 +15.619768015807,6.21439385187069,-45.404649175769 +15.7957528899106,6.24672864329092,-46.3294931319604 +14.3912072069465,7.65344452232423,48.4235160705107 +14.4557573794725,7.6171847648531,46.5102175149776 +15.4705694239945,6.13410127914652,-45.2369377439955 +14.9420529927307,6.1848874187184,-45.5163641946205 +13.9795203010051,7.10310665403583,45.9718862101942 +13.6109557355025,7.11341288485763,46.5102899671539 +13.7290063314943,7.0663020406086,47.0521238906546 +13.5133508026687,7.23990511788024,46.750283687282 +13.4636493640679,7.36264875934352,46.3854770099137 +13.4488047025814,7.35959542293252,46.8121605709967 +13.4162844031579,7.42141023888477,46.4555181669969 +13.8624238766231,7.08615632826151,46.2786928596036 +14.7356619595751,6.37893347643279,-45.3501742646268 +14.6835935440423,6.46516960326452,-46.1089932835238 +14.6452065937843,6.51673780177896,-46.6074533345366 +14.6037113822655,6.49390829042138,-46.7800090161895 +14.5452004046982,6.41263201819159,-46.9432344675712 +14.4116432253485,6.24471551027664,-45.2502183746467 +14.4881611583879,6.39270502233944,-46.2022953798692 +13.4000537630376,7.46572908142789,47.1526655009227 +13.4425104612288,7.55527079049591,48.3445865506235 +13.5497333358851,7.60355683489873,48.9344166128004 +13.6322205249853,7.65720295995292,49.7122599582585 +13.3223442158737,6.54255104533197,-47.3361577907777 +13.2608073892474,6.57072681923572,-49.9144116272589 +13.9390263643706,7.48424676317211,49.0002872999144 +14.000385567274,7.39502202632878,48.653197094434 +14.3397950253796,7.38551836651879,48.4362746639057 +14.3967756698549,7.22017602488506,49.4155152481308 +14.367684217358,7.35587639542374,48.5240449907008 +13.3722768079369,6.6334298984509,-48.3555581896815 +13.3399166814683,6.6504026760772,-48.8694130363956 +13.3762560390241,6.58406227564526,-46.6472577879996 +13.3268917617335,6.38178158168563,-45.1339587044714 +13.2797330607335,6.32280737201287,-45.752405446202 +10.7698463122086,6.3196902759211,48.9506132823831 +10.8168611996953,6.4329218747133,49.7402467120373 +11.0210775113275,6.60920767994914,48.623372081593 +11.1314294688592,6.57658871985867,46.7310054780781 +11.4975088729718,6.54713466337742,46.1611017133195 +11.3641583165591,6.55355562351034,46.6187566284155 +11.2312149958978,6.59287523394921,46.673983019244 +13.9043032474037,5.27801813558139,-45.6542879311725 +13.9050110251505,5.30366235660873,-47.5731472122219 +13.7970222071667,5.70018032107425,-49.8456557322158 +13.8131920687481,5.98179471583363,-45.7605298257578 +13.7996131435926,6.01477187985534,-45.2213451967363 +13.8369677654105,6.07313087184276,-45.6054078720767 +13.9129395864809,6.13281095908853,-46.5928211941486 +13.924022928822,6.18829731349598,-45.4099078415672 +13.8876146830962,6.19232861747033,-46.6627481681311 +13.888681708911,6.19556041886418,-46.7285310251597 +13.8851083098987,6.17011212360249,-47.1615707595441 +13.9186577278141,6.13120786003532,-47.4761869545625 +14.0064446673576,6.13902276305787,-49.6943777655727 +13.9867540363459,6.16784777325248,-47.6168193004205 +15.0921889076831,5.9801220729215,-46.1098363358701 +15.3615971819615,7.25715149344569,-49.2016311792212 +15.2929924405019,7.31199313951731,-49.7709074216131 +15.4172560617686,7.1397454736815,-48.0151943295488 +15.4644120035197,7.03098437377375,-46.785015812135 +15.4477955221033,7.00430898477967,-48.2864725843936 +15.4677628539107,6.98010899429514,-46.1507300519843 +15.3825025543842,7.04810692458299,-49.2393947081838 +15.302629880713,6.95357953153808,-47.9943098443735 +15.2562741775786,6.87168900160274,-48.2155839896302 +15.1936295361179,6.78283389803718,-47.8470241883305 +15.2067140557066,6.73670992172736,-47.5919933835311 +15.2309606077567,6.63022178159641,-47.3622228492592 +15.2494618397025,6.68705922490284,-47.6045065454701 +15.2223174850384,6.66776255914497,-48.2043971176889 +15.1683342458143,6.71514679560372,-49.9569111010631 +15.0976894281287,6.76756359102277,-48.5664615845051 +15.0533598239863,6.77640297103548,-48.3674077038844 +14.9433308920808,6.8802824482282,-48.4074156666778 +14.7683633380232,6.88389746164167,-47.5067070975497 +14.8613815736049,6.88335220828709,-47.6203526760629 +14.6438706792308,6.59943698146137,-47.8876433227103 +14.6298040148278,6.48220515022242,-48.9644041095438 +14.7116972507104,6.41211690505828,-49.610008492195 +14.7265284679476,6.38415056583326,-49.0360834400921 +14.6890724472072,6.71660148095881,-47.0036504230998 +14.7057468654448,6.46250437900501,-47.6070326262005 +14.6648459876493,6.34689840722588,-46.415722660284 +14.6816772337477,6.47546527764785,-46.9349580217757 +14.6381224330598,6.37580782207727,-46.4961704474353 +14.7021625727802,6.85860521986458,-47.0041617276791 +14.5794582142877,6.3379525251346,-47.5054976687011 +14.5128694297978,6.32456511292779,-47.2457204682669 +14.6202196405998,6.36709034074444,-48.8785617560396 +14.3188388578735,6.36910610624776,-45.0069836054669 +14.417686720231,6.29683541779257,-46.724181016059 +14.1179886839802,6.27159117891668,-46.9091561938586 +14.0155712102085,6.3229518324037,-48.1992545849526 +13.3925622543728,8.15836548190019,47.03636372509 +13.3357083154376,8.35196830352247,47.7095566684091 +13.3063500419327,8.61419935517757,47.7664291744127 +13.3097834489526,8.81056992990161,48.289091867431 +13.4336419115648,8.72785189555392,45.7608307943693 +13.3660050813166,8.86124068181831,48.161283638024 +13.4660395920436,8.44446130582552,45.1494308326142 +13.4270181784783,8.01209378123096,45.7848961602855 +12.3091004490629,6.33139941150757,-46.2511913813491 +12.175137712138,6.5971969542929,-48.0654168868242 +12.2065644769286,6.48143056525446,-48.7205123755455 +12.1807730376246,6.72346693612847,-47.9545139148015 +12.1801894558557,6.67777291450555,-47.3892177038005 +12.2041718791181,6.74660315198517,-49.2313243073935 +12.4269252466352,6.43619748095226,-47.3221531987429 +12.3602850308164,6.51650846431223,-47.1425272163511 +12.5244614236146,6.31278081582026,-47.3477249614432 +12.6072398055195,6.2534234330966,-47.0400510316596 +12.6670931160807,6.19876602778175,-48.5841499886476 +12.645094210024,6.18349823525984,-46.9981135861139 +12.5597838621032,6.37129691724903,-48.2593850053806 +14.3763601577354,7.33807173275052,-47.2527954421992 +14.3592793402471,7.42211474453793,-47.3964663499898 +14.3242180761703,7.551062614874,-48.7580323332176 +14.342197800585,7.47712906162842,-47.6365296588504 +14.3922252499137,7.25615559553018,-46.2726113342478 +14.3044560864926,7.3069974590843,-49.2033321042405 +14.0853209840493,7.42188013936929,-49.3099228383482 +13.9326383830807,7.66768303789115,-49.2942741302373 +14.2069773516109,7.3267870544642,-49.7916751774428 +15.4200453914585,9.24090443660283,-48.6206364612979 +15.4471058598931,9.08428687395125,-47.5357436021142 +15.4899558491181,8.85077634033564,-45.9547978330954 +15.5161374310221,8.77003638914623,-46.1573387444307 +15.5369212391085,8.67399953718568,-46.7051225327692 +15.5534098845653,8.65735350052902,-47.0249663402187 +15.5415963135208,8.68561693447187,-48.5875277006417 +15.5193531719151,8.65247867793791,-48.7034508330658 +15.4845447096748,8.7283690967637,-49.0922562992913 +15.4412276426713,8.77581205713893,-49.2846969060727 +15.2782460722106,8.74435603931786,-49.2418791800353 +15.3972051310201,8.84609731503528,-49.4967338121895 +13.7115222136758,8.92037110578472,-48.0413989788128 +15.1987161721366,8.88861004447705,-48.9101882835587 +15.1009217255295,8.90921825293959,-48.8596218452433 +13.7300856154,8.98454826012065,-47.9253570811168 +13.5949279901025,9.03786431265472,-46.8764508647612 +13.8119846840179,9.03302933855083,-46.5058957278861 +13.8605007808406,8.85462635504871,-45.5909130729422 +14.1339344383426,8.31905978917964,-46.5519156343359 +14.0399568222282,8.43240975651349,-45.158363823526 +14.4825617322883,8.13433774329526,-49.5323045292896 +15.0713781638836,8.08143035051539,-49.5249400391217 +15.2806354457156,7.894613179254,-48.8303221268537 +14.2897762669306,8.27978525568812,-48.7609774234275 +15.1800597488257,7.9906740520806,-48.8331518757919 +15.4212208582027,8.10460671017456,-49.8211240541655 +15.3523480731378,7.97082163614432,-49.5109304889106 +15.4752885321777,8.2702529642857,-49.7640011192264 +15.879827412974,8.13645819504517,-48.3965115676468 +15.8652642346913,7.98783773772975,-45.9928268233301 +16.3159495257617,9.20927785836685,-47.9330030152199 +15.8142135039388,9.07467994811289,-48.8955697180031 +15.8787276824521,9.02536728802371,-49.3984379107852 +15.6850572427908,9.48510629388892,-48.938672641389 +15.749472264738,9.33223695584911,-48.2272142459712 +15.3854095617458,7.69879944977773,-49.4167415894894 +15.4356975057402,7.7503390090145,-48.2343531423169 +15.4377204182906,7.73856352625963,-49.3907582504499 +15.3940876739893,7.59626494391973,-49.8998362351313 +15.4066739183397,7.77205189543496,-48.4756926512666 +15.6321440872201,9.64116805388963,-48.0135750594265 +15.5966612567262,9.80470367779478,-46.7864209075971 +15.6174142812055,9.71139409253762,-46.0441388920068 +15.6023089961007,9.7875775509771,-47.058483751757 +15.4946949216391,9.20882164124807,-48.8055922604388 +15.4782376369823,9.06327719236234,-49.6419793749518 +15.4989528344498,9.27989923847019,-47.6392045945662 +15.5042283094837,9.32191239989828,-45.9002125823056 +16.301686963621,8.0758371960308,-49.3221540728954 +16.3341317788063,8.23314201977706,-48.630883794262 +16.3441848470426,8.18717662298654,-48.6994841221041 +16.2850313042933,8.21528913549645,-49.0598915571362 +16.3285306793644,8.25453412415199,-48.790183474176 +16.0688141966025,8.10005623061463,-49.8268312006689 +16.2396035973753,8.12667626387322,-49.8616353577984 +16.1859390906756,8.01201630080008,-49.9609750492054 +16.359738377878,7.98154410466623,-49.8184587257148 +16.4188047333801,7.92629558906705,-49.7912699017032 +16.1617421223762,8.10304954119732,-49.0744401008377 +16.040666848979,8.12538954373815,-47.4215944676388 +15.8307460379146,8.22834018784446,-46.5828746297874 +15.9165062490698,8.20752543781247,-46.9588842690288 +16.1140700312765,8.34056629078932,-47.7346852582375 +15.8517952411668,8.28928939261368,-46.1392153712616 +15.9549533850024,8.3567698168817,-46.4002343485988 +13.8400585494539,9.12918831730284,-49.02620307967 +14.1145536564257,8.84273578109206,-47.9834719151449 +14.2468560811218,8.74493587881376,-48.5983415123675 +14.3416654131489,8.5548005803069,-48.9546372971207 +14.4195769222782,8.60095156731942,-48.8707325864088 +13.9967354951757,9.07761319205794,-48.5663862917937 +16.8688325771046,8.29948646040514,-49.437541483743 +17.0351576832703,7.9675879210166,-48.5446075845436 +16.943062178799,8.1308780370796,-48.7237818151717 +12.1564966484653,6.0445791796379,-45.1854095276282 +12.1425593206552,5.96877517514864,-45.0617072271176 +12.1455891778989,6.14055632098934,-47.2012707029851 +12.1687400973078,6.18665006027046,-45.7931223554502 +12.1203644719129,6.14121642339764,-48.9760817992558 +12.5750623934975,6.62606029411228,-48.9892504571873 +12.5575984381795,6.61946656897848,-48.0887339809134 +12.5808662963332,6.61685724439051,-46.5574489196809 +12.5911776148946,6.56716461588447,-46.2027608033237 +12.6112015160445,6.50428873574536,-46.6448849936627 +12.5922183431513,6.50255789061417,-46.2072315987064 +12.6305801939906,6.51709925365655,-48.9024668264736 +12.6780010897855,6.5999491505717,-49.5108359061173 +12.7348415637802,6.61760642247997,-49.638367628573 +17.2012582057911,7.47142317066787,-48.3493075629558 +17.1187571039939,7.72952758242802,-48.65947038454 +17.2288050396466,7.27802672488399,-49.3729804037587 +17.1608733929918,7.04999535330839,-49.0314095071829 +17.2002884127599,6.9415891206676,-47.523699525847 +17.2582785233984,6.87948424075285,-46.4379825757454 +17.3083312541608,6.8183197375147,-46.2574469453337 +17.3108268112353,6.79402326493252,-47.5857400761664 +17.4219748043864,6.72532369922128,-47.833649501643 +17.5366477177871,6.77244398928299,-48.9012441980636 +17.6433881321875,6.75494808076924,-48.6732806774334 +17.2874703780535,6.82815479923659,-45.6188426132288 +17.8031620809169,6.82326318244789,-48.1978679615407 +17.8854654528815,6.94682390346433,-47.3173812425396 +17.9540532052209,7.05657994001649,-47.0326174874067 +12.8044590164069,6.57126375183402,-48.000556814362 +12.7793488571983,6.62709226393922,-48.8932989188195 +12.8023138049404,6.51417765920828,-46.7965215123239 +12.8621612004885,6.51129040481807,-45.0769778306656 +12.9870242351763,6.61409547457424,-45.7480951098403 +13.0506988307973,6.67859160439677,-47.5865636157702 +13.0600667119278,6.68825883364218,-49.225035686032 +17.9664801803506,7.15146191343835,-46.6649040532424 +17.8144473444962,7.09092937526394,-49.4039672041178 +17.8896342221539,7.16428472546,-46.9352897633689 +14.5780025538648,8.94740560682886,-49.407644754834 +14.6828031473373,8.93792318837335,-48.3049580150383 +14.8500340442484,8.72673806613643,-49.2044096656724 +14.7224361142105,8.94752828433401,-47.819111327908 +14.7814870711123,8.89238852403943,-48.7764827688361 +12.8671960757096,6.5780767384515,-48.261027839017 +16.1355384970871,7.30725352591922,-49.3058669215826 +15.211024102725,7.84230969875468,-49.5760313659188 +15.3113917584911,7.60136525040956,-48.6444258118897 +15.4067400765282,7.52544184412952,-47.8704685385653 +15.5259479945002,7.60362708928582,-47.4664589734999 +15.6610418236691,8.04499403653165,-45.2490167947371 +15.7182435607426,8.08546169260279,-46.1209766553898 +15.765686085983,8.09773728181204,-47.0759491678232 +15.7815425698452,8.06021205573206,-47.7615026995145 +15.6014704078378,7.71217869404484,-45.3419813993044 +12.9880992781127,6.40933001340035,-47.4547603769361 +13.005394392887,6.32102071992473,-47.9465206454784 +12.9433855262843,6.51975136658617,-47.9131119174991 +12.957654558786,6.27050759811639,-48.10735060588 +12.9203188671145,6.39535171703751,-49.6907934668708 +12.922051855201,6.85962375896628,-49.7266415747109 +12.8425752149252,6.95876946977411,-49.5126353545832 +12.7010143718986,6.90643703633335,-48.6135246775803 +12.6329326749125,6.92457294432234,-48.8241173810658 +12.6699301823209,6.96763002002565,-48.0452164748209 +12.7734408355001,6.91610035069041,-49.4355215255622 +12.6480524908122,6.8209664367248,-48.1047925952962 +12.6980073821375,6.92129519703748,-48.1399682600169 +12.7016069739586,6.8252086638032,-48.0026272292267 +12.7050123854087,6.71341251082084,-47.4988463934908 +12.6812218402452,6.64007145773305,-47.0680928207778 +12.6661952177852,6.4989861219022,-45.5009715400884 +12.6176490868515,6.47703622903273,-45.3635608331178 +12.6040509309852,6.44986799573953,-46.5369240654831 +12.6457241685611,6.45262926662128,-45.598269834181 +15.8451882414491,7.91457988388469,-48.2090940275677 +15.7991909038046,7.89709178143952,-48.906574348293 +15.7250372242201,8.08145323701427,-49.118419637732 +15.6887712167568,8.19750075550589,-49.9042687570351 +15.8028520083535,7.94021302986705,-47.9255109189393 +15.8092264696154,7.9138391113735,-48.573800251618 +16.0470464467276,7.51324902777997,-49.0930160851595 +16.0186830655694,7.67117930399416,-49.1420413830811 +15.9405825808318,7.88056377218282,-49.9811796059467 +15.9172397903014,8.05039953995509,-49.5874738264826 +15.8862522619312,8.16318570501952,-49.398342556481 +16.086722330216,7.36652176899424,-48.9120921254155 +15.8525267081833,8.15010901494587,-49.3841698021583 +15.4762574202356,7.97382643468587,-48.4299682833419 +15.4964552017501,7.84702657373592,-49.4094720325644 +15.1020079111668,8.0537995809745,-48.7834445190902 +12.7950340150549,6.79920717093647,-46.228371664965 +12.793100385542,6.77223068248119,-49.8781535911683 +12.7420966801838,6.73235263647342,-49.4944502732585 +12.6829517948404,6.7311158813501,-48.2259343260942 +12.6213493085799,6.7251194200678,-47.6213586963131 +12.7535199470095,6.70671625522832,-45.1636994978443 +12.8170183339499,6.84468817056668,-48.4481009205856 +12.6043838315724,6.70296661805608,-46.52745770916 +12.5750753986591,6.77954308709574,-46.4012140243616 +12.5803653067399,6.76345764861165,-45.6354906665872 +12.6071546241384,6.70438726894353,-45.728802781312 +12.6058309194267,6.53734891092248,-48.6165010702149 +12.5996267587317,6.66163813169241,-47.6720908972812 +17.0614611650494,8.11498848083222,-49.2256039441558 +17.0778159702805,8.18342452067561,-49.2176029831983 +17.050926972781,8.00626080231365,-48.0667196875573 +12.5856018918821,6.44446162843163,-49.7540351077159 +12.2987267342159,7.10458597472325,-49.5685828984179 +12.3121003704438,7.20719266276599,-49.2366227441324 +12.3624900462982,7.03257759131104,-49.8842842546292 +12.3332710317425,7.15865942244077,-48.08840214717 +15.0582996621133,8.7972551058149,-48.891554345093 +15.0232953652019,8.75633027870776,-48.8096838343021 +14.9555179993571,8.76907028645907,-48.4299447858193 +14.8313837011236,8.42179228188722,-48.8104749506305 +14.8723861104569,8.62641842366815,-48.5879370999549 +14.735877224533,8.0933316264955,-49.6286531924273 +14.7829085051655,8.22629218447929,-48.6518838093385 +14.6867475785645,8.07748854338645,-49.4289987916616 +14.6712138400882,8.18668253918812,-49.1816338328558 +14.6926127609011,8.05885487280255,-49.3885145982285 +12.3919113143708,6.84040332011865,-49.823942243172 +12.4292194535265,6.70499333618146,-49.8133454670642 +12.4373991662434,6.63030631659374,-49.4405228112113 +12.4240379512692,6.61781600515128,-47.386139552074 +12.4412996662105,6.6045054070296,-48.662827790679 +12.3751477829507,6.63661496433506,-45.9098766014802 +12.38703802732,6.6168310512234,-45.7895589421568 +12.3484927896833,6.660093838678,-45.9973476977335 +12.3193418985337,6.63394148277764,-46.6256072980498 +12.2924130487302,6.54339914961923,-47.9023975149745 +17.0255101006026,7.84402512980144,-46.419729250989 +17.6319157134302,7.5037264679839,-48.0235428030524 +17.4864763079053,7.48834493572653,-46.6337871744531 +17.8918924919781,7.65482072746627,-47.0730153917932 +17.7582349068599,7.54757055724305,-49.7984144650934 +17.8695911641202,7.61921568562845,-48.4385650799305 +17.9134342425175,7.67670837277702,-46.8571790950537 +14.260511568219,9.04831745304119,-48.8521786157069 +14.1899723289862,9.06937397608204,-47.7821100224975 +14.1348823923241,9.04652534899223,-47.4566527891201 +14.1791842634848,8.8554205548635,-48.9985648429217 +14.1754993395512,9.13827218436446,-47.4766163665426 +12.4369550985664,7.01895544479132,-48.1907260160379 +12.3978980298236,6.86845679508255,-49.525160409073 +12.4437073336855,7.22863933744627,-47.1354998587215 +13.9592965142987,8.84041326199337,-49.526466993627 +13.5381436694464,8.50082504652835,-49.9279409364277 +13.4840713947636,8.32494156586611,-49.1091912555427 +13.4380526567896,8.3064423669488,-49.8907750805804 +18.5907173268691,7.5710776567108,-46.6200608905433 +18.593038713137,7.56985625558968,-47.3684421859772 +18.6192849882804,7.57573516398921,-47.0119105517119 +18.6271450094737,7.69114838481415,-47.9623027816802 +18.6187668523466,7.77781864619723,-48.1837147500447 +18.7110195189356,7.82805214884149,-48.7213769371432 +18.7387710775007,7.86995203643908,-49.7182768926381 +18.7458010328288,7.87850513606519,-48.6199712626679 +18.6736059046517,7.90268103575409,-48.6893758744961 +18.6689695251222,7.8052495192573,-48.1182386468533 +18.7196784018921,7.89722409412621,-48.8032199320386 +18.6328406593252,8.00828788548202,-49.617404484439 +18.5917962189936,8.20930774242773,-49.0891337334882 +18.7537409561312,8.47853031212714,-49.6125605925184 +18.9056556203564,8.49667647558212,-49.6835850031725 +18.9742065785785,8.5504348879955,-48.0664947381372 +19.0287984473945,8.61950544018462,-47.4370149630981 +19.0626497207802,8.65670558454295,-48.4536839310003 +19.047473843914,8.68267431944015,-49.2725612572559 +19.0781127254169,8.65327126679955,-49.9998858051883 +15.0770904780716,7.13252367193633,49.9865257370109 +15.3617256983192,7.09367262158953,48.131463898938 +15.2258331558505,7.11553540172962,49.404729080944 +15.539836535368,7.02451697006093,49.1263227594293 +15.4322490818756,7.02984752664492,48.5093960250154 +20.0105064336796,8.5805702347534,-47.3861221383347 +19.96837723269,8.48834043211874,-47.096468549289 +20.0525942424731,8.71001656709865,-49.3370688602011 +15.7582605897626,6.91358497643709,48.8934160875328 +15.6888381289238,7.02938960315671,48.4118721419425 +15.5786508880856,7.25672256353647,49.3868964129999 +15.5652748072216,7.18778834106955,49.1072462273468 +15.5715497808909,7.07458062847141,48.8872966919241 +15.5920688453915,6.97334762013377,48.5148004302663 +15.6244052475429,7.18891049310642,48.7345734396704 +12.4385962181138,6.98608574113234,-45.7933774101347 +12.3442412433416,7.11069635486273,-46.586895636749 +12.4069803808028,7.04691303411332,-46.5831963428767 +12.2856527454392,7.27039351980877,-47.6491359605908 +12.2078122329802,7.45132839173986,-46.9396720262917 +15.5742745261488,6.86276680260071,48.0538555291264 +15.5353513317249,6.80977095740882,46.7094009098998 +12.1685445870045,7.53555036140205,-46.4236636959244 +15.559902146932,6.76876697090486,47.3750625750021 +15.5072613230886,6.82161153846488,45.8711105916128 +20.0123665941017,8.36857117467304,-47.0846042416534 +20.0888580461071,8.28257418049912,-47.4077103591148 +20.1291269994435,8.20038956900713,-47.5886762171869 +20.1067921747643,8.19672819719555,-47.2393626159368 +20.0865823262277,8.22427893093224,-47.8999531448179 +20.0851478700424,8.25692716149906,-47.4270946127159 +20.0308099427552,8.30239330138014,-46.8397187956965 +19.9203469458348,8.2813458970456,-46.3198596645687 +19.8646176538685,8.26194027468742,-45.60674166877 +19.8460417185412,8.33713648125152,-45.5536974588136 +19.8127438081435,8.50514949012559,-45.3994084504261 +19.8406232661527,8.43771649713262,-45.5162117300671 +19.726430172617,8.71586516874542,-47.2113492412008 +19.7016553956178,8.66082685177838,-46.7476600502081 +19.731350935573,8.58131626440792,-45.9929831816998 +14.9079497798066,6.67954013713716,45.4037032723433 +14.8778804816119,6.78996953061739,45.2868059383862 +14.8402722169709,6.88539418053244,45.2304412830456 +14.7705057090264,6.97104318330595,45.645161337155 +14.7071488739881,7.00641371098471,45.9844422507625 +19.7947620476824,8.64013864383972,-47.3217402285713 +19.8584403807419,8.53838480161388,-46.1472276866055 +19.907862588749,8.42627926370942,-45.412589677649 +19.9017844553773,8.42264097847594,-45.6731245991418 +19.8764790592379,8.47527221932039,-45.4483926573817 +19.6338860113363,8.61239827560258,-46.0123503362392 +19.5699025580097,8.57699146180131,-45.3668273654411 +19.4992137334984,8.55517281657972,-45.208248932986 +19.3659536268012,8.47013002248718,-46.8239485726507 +19.3468593955577,8.42805925838134,-47.8371016847504 +19.4008773323442,8.38831221401367,-47.9376682676686 +19.3933525554525,8.50865628365715,-46.0107632267575 +19.3745667064818,8.44924390117375,-49.0783920126506 +19.3271955953735,8.46252005654532,-49.0287940737234 +19.2415275638062,8.460304092527,-49.6597827534773 +19.4297230901025,8.45194589223578,-48.958160060069 +19.0302660904855,8.51924753971949,-48.6849117727821 +19.1416432183447,8.38790997668646,-49.0560507087662 +18.9810784819984,8.72337967977509,-49.7190082109526 +19.0932116051443,8.49890069656661,-49.3196142731198 +19.0013294098904,8.62224791095945,-49.1208559538488 +19.1313732948062,8.48151526977436,-48.8302359661323 +14.3729048265073,6.83338484687443,-48.8631061043441 +14.2718330525424,7.04284494501689,-48.8343263734293 +14.1946870506099,7.01560465105932,-47.9526759975124 +14.1188789244141,7.01363172092134,-47.4059629408324 +14.0488771326714,7.12114607921547,-47.1927176626655 +19.1311128679861,8.48520207312644,-49.9462238645487 +19.0024907093556,8.69468069754765,-48.2977651154733 +18.9218392251613,8.70313447355793,-48.8919266080187 +18.9015416557769,8.63664757634381,-49.4834165357738 +18.7566684242951,8.53581015216509,-49.4359756742694 +18.6958187620529,8.48308187480961,-49.8161105103038 +18.6891409437635,8.46362288066823,-49.3463862412825 +18.6981419150589,8.52213224250129,-47.8991095319678 +18.7202007921894,8.50032316769811,-46.9209191668046 +19.109231573606,8.56079449507665,-48.6150970266656 +18.6949259957316,8.36377930471303,-46.3496687409041 +18.7375625157082,8.46561155702639,-46.3692280592446 +18.62432751553,8.14588935809844,-47.6165905565348 +18.6941727612541,8.23293182359307,-47.2884855169155 +19.0536750081758,8.59142264667295,-47.8134091076355 +13.9982342618582,7.09458228601383,-47.8145394165014 +13.7583431275428,7.12899200454663,-48.05751251948 +13.7018791652327,7.10746664017903,-46.5081243032579 +13.6763656519174,7.25315039675405,-45.4722033444707 +13.7045803857534,7.49470635785191,-45.4111082984619 +13.7082632435474,7.64107859625613,-45.0414759181446 +13.928277131505,7.08157663371419,-49.5274613295134 +13.9560497843189,7.41844369862424,-45.1421245142604 +13.8244596313142,7.55916329432509,-46.0020571823922 +13.8796666570465,7.47541515154601,-46.0645634192426 +13.8278788865772,7.49784594868779,-45.6322410989128 +13.8060550685888,7.58890675657846,-46.1875102599683 +13.8133621162331,7.95519355382897,-45.6505765846722 +13.8876319258397,7.99250530090793,-48.1314455641212 +13.8475935848248,8.02722944092665,-47.1127991009186 +13.8898294023056,7.86369661520389,-47.8250743804409 +13.8768250624158,7.88434226868038,-47.892100896807 +13.8682832668055,7.6427844453108,-47.3781197042047 +13.9040787469879,7.73294257425329,-47.5687126200116 +13.7827765286427,7.5210497507265,-48.7890161668654 +13.8351122935391,7.62891131834543,-47.8808816679248 +18.50028727725,8.19471740438052,-49.8697369538249 +18.5152538422277,8.13908798070828,-49.1001168257508 +18.5494435514313,8.12298496413971,-48.4033510947797 +18.7625138791914,8.37601085696816,-49.2747926136882 +18.702573627551,8.49394840141435,-49.7458954368997 +18.863395012902,8.23476389781504,-48.6845457970237 +18.9693303935279,8.22055247456179,-49.0879840331491 +19.051265399278,8.11841050772943,-49.2555521093099 +14.0118342929878,7.17219066599651,-48.38932896239 +14.0220771573668,7.11922658426644,-45.9832869067229 +13.9338709914454,7.08466446465188,-45.4818343298807 +13.8569724273914,7.02052235259275,-46.2832867834361 +13.7458840849372,7.07935217064178,-45.9604814995361 +13.7847975146766,7.07777871612198,-46.7538688137874 +13.7345283761898,7.06489736159992,-45.1169952613488 +13.6989997323538,7.02286423045764,-48.7295392771589 +13.729236122365,7.01484183249942,-45.6298305816032 +13.7381745131657,6.97994822438298,-46.8739049939836 +14.7758462445699,6.58335428565822,-45.6133076338712 +14.8231437494154,6.56102181065743,-47.255094136269 +14.87638379654,6.6183279609214,-47.5231136037429 +14.9340885324109,6.74686626595916,-47.1516769679954 +14.9766082997588,7.03269992838483,-46.8143802594557 +15.0436538141911,7.17083924757557,-46.6721632461636 +15.0397497861051,7.14080698636126,-45.2085303029408 +15.2561248105504,7.01672152238068,-46.1693739118298 +15.302501131644,7.14023571915929,-46.9700658311226 +15.3732361377344,7.13698518310184,-47.0572507011918 +15.4019780149579,7.09237736210737,-47.5216596153574 +15.4314714970418,7.09531881762383,-46.8131060333708 +15.4153723008025,7.08726277807468,-46.1733491279676 +15.3926749093111,7.0605671875733,-46.756227954667 +13.1938681403438,7.0867053132958,-49.8937753449157 +13.1633839200542,7.16540293013192,-47.5145546903981 +13.1120158744128,7.28365404400825,-46.9777895027706 +13.0829840094287,7.33931837253786,-46.852895656967 +13.1751879770472,7.06491754944707,-48.5533133094404 +19.2616220900619,8.07392902749501,-47.9897351071562 +19.3312391784858,8.05175963218496,-46.6472169767513 +19.3930652306124,8.03915261180167,-45.8982113144401 +19.4079557109997,8.02498002300721,-45.6866456508492 +19.5056277096342,8.06352943162718,-45.606476291113 +19.1628218553432,8.08917441281718,-49.5302017163168 +19.7074107734444,8.26103646107765,-46.9759562094752 +13.0597942594381,7.42404383939737,-47.3688828309789 +19.8782791414426,8.38843944325002,-47.9769966102432 +19.781645374982,8.33701141372186,-47.4388511513681 +19.5982494166497,8.17543383115841,-45.9533201283856 +20.0329749902258,8.40531437733693,-46.2013046254124 +20.0919044791288,8.36198921461606,-45.1432888950436 +20.0782872292087,8.44029262499818,-45.2574010347936 +20.1041659119856,8.49922593723849,-45.3563803022175 +19.9670584358301,8.3775847876654,-46.7520004434706 +15.344383987944,7.14775449451833,-47.2515325756644 +15.2438808927545,7.20190747510866,-48.9027084058139 +15.1617088281105,7.2795757800651,-49.9767156796177 +14.8544587592178,7.36118057491628,-49.8344733679744 +14.8084814846104,7.38918579133723,-49.8622868830722 +14.8184578525283,7.28120813228126,-49.4850652458041 +14.78607717222,7.25953894919189,-48.6710409143634 +14.7336596708959,7.21278774232637,-47.9537060570381 +14.670254714322,7.14376327898889,-46.9167089670982 +13.0972284213815,7.36811557807419,-47.1627604921382 +13.2040617494302,7.37176602999999,-48.8930446054879 +13.1619349658928,7.35894025489142,-47.6320717387298 +13.2388372913619,7.15898154090531,-49.3730460608195 +13.243155063856,7.32245273903729,-48.8300064858507 +13.2810605071415,7.40022485724187,-47.6245508908813 +13.3268079855031,7.60588156217811,-46.6518144831826 +14.6542369039414,6.96268129992642,-47.6832599142981 +14.6352217243968,6.9743718556434,-47.7089895579157 +14.5943528142733,7.00093757133368,-47.5242015309601 +14.5947329195496,6.95356859829106,-47.4720827324671 +14.5835217640058,6.97688044130689,-47.4873595456214 +14.6068039744805,7.02417589443775,-47.1053938189746 +14.664151855991,6.93702809043861,-47.1797099151433 +14.7265138007879,6.95155670149251,-46.61816467015 +14.7855997755597,6.91405187454556,-46.5184900366107 +14.8652613377384,7.02464920416458,-47.0880133014635 +14.6195765401562,7.07560565292808,-46.4155677845204 +14.9017600490506,7.08828412442919,-46.9306766254387 +14.6228728590007,6.99785273197737,-46.9778775775863 +20.0694091452579,8.55758515613552,-45.8424730201143 +20.0308570977679,8.71303649229964,-46.9317023940757 +19.9735997737765,8.74554848251383,-47.1451845419674 +19.9447512479964,8.6996901753544,-47.4252671209308 +19.8844794567138,8.57664159579721,-46.9673014654726 +19.8634183294407,8.51507984654545,-45.7384113348588 +14.9632652820608,7.06418135254449,-45.604288574104 +13.3781807811802,7.65184173307545,-46.4412469337464 +13.3263335468435,7.60450927286051,-46.9553265924969 +13.3684518953834,7.61406763075525,-46.39685661679 +13.3738684147357,7.69232869519306,-47.0150424728481 +13.2995275885474,7.60476441401594,-47.3127573239009 +13.2763492762768,7.56679964044412,-48.2280699216247 +13.1652536741004,7.64194235594878,-45.5808017404789 +19.897568149943,8.65218253137338,-47.3766892874337 +13.2202665243179,7.6133287232545,-47.1891707765209 +12.9768312460611,7.19846715370341,-45.2553412309709 +12.9406286145565,7.03952336238594,-47.5994222900424 +19.142697640331,8.00638797056438,-45.2081296310063 +19.1362756921767,8.21850118529717,-46.5521995519063 +19.1342685357232,8.40454689290307,-47.3424895201072 +19.0774623548872,8.60113031483475,-47.2658814904582 +19.1323195510464,8.5533638098788,-46.9356126325085 +19.0557312006383,8.57160321117717,-47.053039889161 +19.1453271454488,8.32982411237369,-47.4978210192189 +19.1822886841081,8.18041809150459,-47.8626736368876 +19.0502403487449,8.47069723491366,-46.7998233324517 +13.4806680428788,7.16528122924334,-48.9969188518908 +13.4344949682479,7.21521201904413,-48.4080139252761 +13.3660115551723,7.39693103818534,-49.4699694798881 +13.1887106485994,7.46135744904947,-49.5661068828114 +13.0548679458839,7.3350552661449,-47.6392817470643 +13.1418898924355,7.3888693280502,-49.0502872629345 +12.9531746941446,7.27589677358862,-46.2312378376791 +14.6688860032249,6.16768497892058,45.4410578225466 +14.7063834915704,6.1481751832415,45.9341818279667 +14.7558613078653,6.25421557345173,46.2705635879597 +14.8839121338016,6.26466605931313,47.5038523469034 +14.8083926087238,6.29736791029618,47.2253013143551 +15.0378492431263,6.04033235798397,47.2223358235295 +14.9697111139297,6.16095914092505,46.9116755376225 +15.0612441293452,5.96395320077535,47.8165570491309 +19.1864714609824,8.20076121265652,-48.6560159515457 +19.1502788354638,8.26075398679064,-49.7232358293975 +19.0751008834771,8.3521158594674,-49.5354306485849 +18.9862058461586,8.49878380021964,-47.781928726935 +19.0257032170765,8.43330748433292,-49.2045150650636 +18.9054642265266,8.49176197149858,-47.3981122262399 +18.9367602139433,8.54949850725862,-47.4307745943029 +18.8631236284087,8.3072783900894,-46.2129195278345 +18.8924733732595,8.4232710216353,-46.872707427795 +18.7883365242036,8.09704845717933,-46.7807070391816 +18.7729010563971,7.98209239519043,-46.0973144305689 +18.7490923221751,7.90625850363319,-45.4715749283152 +18.8273082076449,8.22805404577844,-46.977768114849 +15.0691222715322,6.15164199931763,48.8981134526539 +15.1169553326353,6.02316654096569,49.0591123948092 +15.013273363252,6.23693011264762,48.7721884020235 +15.1074447086059,5.98528620968951,48.0308128802931 +14.9611365027292,6.41569206039931,48.324276863105 +14.8732851605753,6.50710599830346,47.0705967633496 +14.802746373503,6.4784551266974,45.8226106037958 +14.020512829006,7.28178971710132,45.7343851800127 +13.7366766297201,6.69068513373579,47.9486190849494 +13.6934884052317,6.72585843935318,49.3946013284417 +13.6775380289459,6.84757436428499,49.7759711009408 +13.6973972913293,6.83511482674144,48.8803775963956 +13.7909003749043,6.83052599985402,45.7047509025637 +13.7796681578527,6.6905062990547,47.3526538486342 +13.8559022417295,6.67574727005669,45.8289889913569 +13.7189502698981,6.74841262872059,48.5042728197205 +14.8868561544699,7.26409735626518,45.6939372659778 +14.9551004031751,7.26699062460987,46.1668191095677 +15.0221177954093,7.31613757462779,47.0282174748481 +15.0833095759604,7.34510623345548,47.0613030717237 +15.1134101457006,7.32747397886439,46.5889299949185 +15.1503186787314,7.30030999750937,46.2623106851225 +15.1748356799531,7.25824315231634,45.67090425306 +19.1530901275878,8.16865874302916,-45.9409682535645 +18.956117759684,8.02653617106225,-45.935645555831 +18.6767427693315,7.97379710181209,-45.8317834183443 +18.5747004649153,8.02125916964166,-48.1481252453511 +18.8256080990631,7.97680624409409,-45.7561200281609 +15.2035290235225,7.23364116811615,45.670949700291 +15.2330304178058,7.25819145382553,45.2165297833054 +15.3361560412232,7.20544478535246,45.0128792217348 +15.3755873299025,7.20556470268035,45.6480917117992 +15.3416918190126,7.18622987117397,45.736942895591 +15.3603569289116,7.15312721174258,45.1027888685135 +14.156170065263,7.30796504351662,46.428930477532 +13.8462310541031,7.09187466161107,46.8231071025214 +14.0349668827927,7.23792392272808,47.0195285089687 +14.3139298453455,7.40627328473451,46.0233909469449 +13.716717528888,6.97354575322369,45.2444601938767 +17.6905359651107,8.24082488157821,-45.0467894975947 +12.7775851836605,7.26172240153378,45.1944339506892 +12.734947020708,7.20879250602427,46.6849618924832 +18.5259607088,8.13795867149008,-47.0442073371345 +18.4826996608907,8.13224763162718,-46.3907612874463 +18.5612627358802,8.0976211471135,-45.3295625384674 +18.4802272328244,8.11184917738753,-45.7048925827958 +12.4728917308022,7.10221570041316,47.7835527767046 +12.3653853915427,7.00238939027585,46.8166986725548 +12.2711699071943,6.90724991068214,45.4624587983535 +12.6475918089706,7.23229266887442,48.227449364707 +12.5637474685605,7.15324045113019,48.124400136104 +12.0787588307678,6.90311687548206,45.3942954894362 +12.051718487291,6.98942833701825,45.6134748799097 +12.0799614828333,7.03221492813182,45.9454068293135 +12.0715085204262,7.1297361883743,45.6604387988599 +12.0368989818274,7.28301200470514,45.4927671464848 +12.0743131988296,6.91439058479691,45.0309688887473 +18.2398787653977,8.39220998734318,-46.4475412329885 +18.1755132256003,8.40396000049591,-47.1301573142731 +18.1158990036709,8.35356422524064,-46.881483452685 +18.2862854294621,8.44148761130894,-46.2416809506811 +18.1461158209058,8.25379425448724,-45.0964488953377 +18.2272416462289,8.21330747950209,-45.343592884959 +18.2169121601361,8.39407243087491,-46.469780116884 +13.4256550644174,6.79665320163408,46.2399850227405 +13.4751430100156,6.71575090814215,45.7741334236399 +13.4803112020885,6.64364664270085,45.5292769172376 +13.8923895191079,5.9436979078132,45.0697968461429 +14.0311134129831,6.57377920041828,45.7560683280153 +14.000260327948,6.82128332714099,45.265881482606 +14.0234220632915,6.28816794339409,45.1325486811407 +13.9257376107278,7.08678562730534,45.9845755210417 +13.874855132266,7.29897730569141,45.2277412587475 +13.3310901774874,7.09875563777471,45.1164996954068 +13.2863471784349,6.97910331148338,46.2807500607751 +13.2368402990472,6.83784064880395,46.6106894920996 +13.1901708828968,6.71165477429869,47.078752532304 +12.9991679126584,6.56402631516677,47.139490363689 +12.8624513094205,6.56067420392027,47.8376029917147 +13.1157532895388,6.6150692453867,47.4371271788614 +12.7131277365382,6.56046252882033,47.4009570812269 +12.4580512149908,6.64837510402268,47.6622502086886 +12.2403736900557,6.62177082673467,48.5140569934113 +12.3399909632609,6.63865039290393,48.3105200681208 +12.5567133961778,6.61920795109097,47.9557927598759 +12.1783250244151,6.54941808626506,49.0279030805987 +12.1920698978716,6.39759911880877,49.0778698619241 +12.078558531719,6.22657892627075,48.8739397749104 +12.0057529711635,6.25625313241208,48.2050325592108 +12.1450636773832,6.25394531984804,49.3346422977284 +12.1923812237588,6.30721982039263,49.5403070341843 +11.914919195866,6.1951921379866,47.8455303527455 +11.9550888653047,6.23707180869855,47.5231031801056 +11.9470848297819,6.22390756475569,47.7950465064738 +11.8438765585797,5.99300888418396,48.384952139553 +11.8414911115927,6.03858244294524,47.7202703637113 +11.8655185120218,6.05647177820223,49.4445069578784 +11.8461569352921,6.11723126565678,45.0188185283495 +11.8907030468002,6.09924949322989,48.5199987573505 +18.2585926893984,8.49933738237473,-46.8204258761895 +18.2461610703271,8.59983783780339,-47.7184880172043 +18.2439487609236,8.63497129301541,-47.5964129444895 +18.3365338612108,8.51954133076578,-47.4470650615854 +18.3940641211422,8.38204600635485,-47.4570153523077 +18.4476615891542,8.30470260962652,-48.2126483065477 +18.4459376742761,8.23620485824656,-49.2374031651854 +18.2789049641869,8.59905316115838,-47.167401736481 +12.7112108963551,6.06090960197889,46.850285735363 +12.7276369456101,6.22765507725763,48.4310927978657 +12.6582489783321,6.05596918041596,45.6505406464147 +12.7710271119574,6.51727640372605,47.3934078718124 +12.8183322842822,6.69323281311167,47.0098393184761 +12.8804045885278,6.83599080200454,46.1873043447894 +12.7551626823812,6.38338289123781,48.4641869590176 +12.9438762090444,6.84456297708637,45.506372325326 +12.8601877828007,6.78075140567939,46.412401848175 +14.2691538613776,7.99782847549888,45.6787508788469 +14.216558486155,8.02058462090861,46.8310430931334 +14.196310717019,8.06358809366031,46.4636128218222 +14.1853439275103,8.12225065853555,46.0230674025875 +14.2016306573777,8.14800632098881,45.0539677117642 +12.9687012358198,6.81502289990299,45.4008461625979 +12.9538894821163,6.82558407190915,45.3388520481035 +12.9443777222513,6.85097895765723,45.3155320686915 +12.9164201116451,6.91069452563134,46.4777438795899 +12.9828049529641,7.05689927666811,45.5378833359716 +12.9807821880504,7.08244368259944,45.1168475562005 +12.9105387561796,6.97445953727868,46.0847403122389 +12.9367032007914,7.00216215930609,46.3126086190704 +15.6009927831311,7.65019597075996,45.911153013465 +18.3110211551793,8.2033303564009,49.2115990940037 +18.4269062798213,8.13294725313666,47.9829717835559 +18.4988243238911,8.07282780449538,47.7042638850428 +18.3155653420607,8.11788974648432,48.4204896569889 +18.5847893361465,8.0667749919936,48.4422538040993 +18.6201382711812,8.10729793101692,49.6818039535178 +15.7259161422099,7.72723138768137,46.3165969805621 +15.8614266063718,7.67328838167039,45.9478116479037 +15.9572781805378,7.60473940740013,45.3261364876723 +13.2206133925567,6.55397005235277,45.4323395259883 +13.239145807896,6.36210134083639,46.2664265553062 +13.2042510562598,6.23321237035352,46.9039993311898 +13.1500574806866,6.40520495532316,47.1958688807139 +13.2198002923599,6.29764639657026,47.1868033703622 +13.1617128369089,6.55758667589598,46.6880751300679 +12.9106573712459,6.81785180421179,45.544534614291 +12.8728271089632,6.7025693974661,46.625503223181 +12.8411270180017,6.58158006868986,45.9558867281499 +19.0148913035805,7.93008509500209,49.6627665761336 +18.9994203507692,7.94213256416283,49.1028520304112 +18.8764357079217,7.97365945265829,49.5721999867074 +18.9470453817133,8.0097435913723,49.2051714638134 +13.5528277891336,6.76444449204669,45.6594376209726 +18.5988137357569,7.57498365749605,49.6683244983946 +18.5548575869164,7.57821834952149,49.8853416710951 +13.0114558499847,7.40119428148862,45.8595145797002 +12.9092390063976,7.68786542570596,47.5525211024815 +12.8394466527631,7.7445750493793,47.1580055396156 +12.9998300129259,7.3000893339371,45.2724648536116 +12.9466671305898,7.57637476566217,47.3018213295028 +12.7391182905504,7.83284270792834,47.9324110391136 +12.6963606992506,7.73203103750492,48.1099088948525 +12.3558177031625,8.09575513849724,49.5553122414115 +12.3133437332479,8.26732714679592,47.6760882406807 +12.2678224138802,8.26019757083996,46.0995546708105 +12.7930909596261,7.87568668819565,47.9932336378424 +12.6216525840246,7.64349606632635,49.1047679501295 +19.3216534043548,8.36793675692596,47.7284920333841 +19.2777942851866,8.05761577542899,47.3129630819772 +19.2899029785749,7.77169277520174,48.2220823977967 +19.3879456030737,8.71455596320812,48.8687995747639 +19.4255180498402,8.6410638899879,49.2225417098925 +19.4278307288615,8.44647290771805,48.9669655638189 +19.4299510724186,8.36692716699567,49.735243524144 +19.4571234114768,8.20472891349161,49.7735413848529 +19.3424649616577,8.62598461382601,47.8431508457487 +19.2672817746879,7.4458584847858,49.0540032445795 +19.3836588318326,7.88455942764374,49.8470091084071 +19.4183281297909,7.79511365179486,49.069870055753 +19.4379817405252,7.81729524130204,49.7198360958215 +18.8236639049599,7.55864935066965,48.5918911548338 +13.5836175126796,7.49249101115964,45.754475686091 +13.6980017273001,7.32983687694778,45.4070248854351 +13.6251126623892,7.42704463686005,45.6749707030387 +13.8511197738005,7.26460955059244,46.8106962949775 +13.9636352933405,7.23752722602139,47.2720243915946 +14.0001294164875,7.23059539669173,47.1055037473663 +13.9998001640284,7.33946833943823,48.1885891858369 +14.0384313221189,7.29528222922845,46.6712697838755 +14.0863033705131,7.30162866389471,45.9197542741539 +13.7750654294788,7.29778465431814,46.2931222929359 +13.9092193624539,7.30715544176214,47.2859067158752 +18.7523566528477,7.34047647409478,47.2557791079472 +18.6278842336732,7.19334862385314,47.364489897026 +18.5592791087925,7.26693867792451,47.7116509889809 +18.6721494923099,7.17885512208106,46.5962015695997 +18.2943738956484,7.66177525054936,49.5635868228234 +18.4560123088843,7.4363469465594,48.1819752739635 +17.6617787201496,7.8672613320876,49.9664782929215 +17.5344287716481,8.08492765766049,48.7764011914284 +17.5766179621649,7.98561093038728,48.6529627801 +17.4665483515832,8.29133254277928,49.0874007756431 +14.5584824261761,6.82172017036488,47.6200561162971 +14.8078303140085,6.81988291202085,48.8832224362073 +17.3976685477514,8.36088046931573,49.3979260044792 +17.3829187201079,8.41558035764857,49.8841569460032 +16.8599930843556,7.85764779215725,49.949871646998 +16.7307148209618,7.98550632898674,49.6894461164566 +16.6287377985596,8.10248605718401,49.9335867648305 +14.8690743115628,6.84046570435376,46.2933788189184 +17.2605752923784,8.29215141237336,49.5882459222963 +13.2554762254753,8.53799706801814,46.1589363019271 +13.1833578382027,8.31388176809924,47.4593358371953 +13.1412470797393,8.11162289832406,47.9644805935057 +13.1607417204308,8.0485154030834,49.0408933464329 +13.1143386427008,7.98606769198764,49.183239376233 +13.0614102041772,7.97339348523052,49.0827227091285 +13.0480248750098,7.98559265770132,49.0002122505413 +13.0254438726293,7.9761561584996,48.4414230647947 +13.0565206236385,7.99473285840338,48.1248572751129 +13.1080783567943,8.01712735185759,47.7820427758139 +13.165883181492,8.08327025713628,47.8259059663235 +13.2718570546695,8.20066597659472,47.3468877873859 +13.3617834067473,8.28891321903253,46.36337116114 +12.6060924103425,5.93905654859119,46.9045590254266 +12.5381292656965,6.06776915540269,48.173323889599 +12.6536561212173,5.97446097807777,46.4998352512216 +12.4186843564113,6.37079552798234,49.4155487543084 +12.4865500478975,6.1965184589767,48.8439311583174 +13.4097348255496,8.35026711906965,45.7241200478972 +13.5069325573842,8.35292086039169,45.8107422510834 +13.6593211981015,8.36781543303919,45.9111556847209 +13.8420756790488,8.28935938856658,45.7645842621927 +15.2605598621813,7.39334486134465,46.0216310578696 +15.3443739653101,7.39170084864287,46.4870642697296 +15.3538419736151,7.41785597334631,47.1314110609603 +12.2931254554307,5.94743844929852,49.2528647719348 +12.3098689813939,6.17834987898935,49.6834601641703 +12.2770786409254,5.80865866508546,49.0740834682073 +12.2604733184464,5.69943089843092,48.9433569449742 +12.2683559343226,5.6900821161038,48.1831934133356 +12.2918970840733,5.77638446885969,48.669506298747 +12.290318913142,5.971975364643,48.1043723171662 +12.2857814145532,6.11492137349724,47.1513069473028 +12.2952894802796,6.154373582113,46.0576854026394 +12.2554299012283,6.24826914810427,45.8798069839884 +12.1669498844854,6.37509205962163,47.3791974890727 +12.2063435703691,6.26924499209439,46.9107649222303 +15.3891594981157,7.328351939453,46.8186762819419 +15.4650932476218,7.31510453738643,46.4917888737987 +15.2227437235269,7.59694343263643,49.3952379927985 +15.2027527431084,7.35781596551128,45.3913958383018 +17.3011771521616,8.03763834918234,48.2326418324563 +17.337002607096,7.83842392626302,47.0824725839594 +17.7480833620371,7.48506688734555,45.5372918751302 +17.8329816731051,7.60989918727137,47.0602894190993 +17.9114317778031,7.62521184188725,47.9323699194373 +15.506591631548,7.33783246935467,47.1153041568851 +15.5244333377003,7.47676335288684,49.467953100161 +19.6635639546895,8.01968894049844,48.758625432946 +19.7494576667741,7.86218364209255,47.0177787481073 +19.7938840608971,7.93219695956646,45.9428664687579 +19.743982726992,7.96367469523979,45.0070238106942 +19.7822884716782,7.86921811606208,46.3545134316727 +16.4013864488881,8.97984911264022,-47.4729305894093 +16.5906980023435,8.67007215029487,-47.1718058476666 +16.7981002356544,8.44492348536958,-48.6626372982159 +16.9161560231327,8.12494177835644,-49.1327701818597 +12.0781090203285,6.4536793237564,48.3434339132249 +11.952287279361,6.48877914169778,49.0073644656906 +11.9184590135754,6.50134918330388,48.171746597276 +12.0117866294519,6.4764204223329,48.9775948358698 +11.8007052666263,6.4748647100151,46.0794244330028 +11.7718428139589,6.54507672257942,45.6051183324523 +11.7751895090126,6.61191485470123,46.5783062912973 +11.8479345569239,6.4373923985259,45.8391169379554 +11.7994494740851,6.65099087554877,46.4485823370242 +11.8306316596137,6.56707718292107,45.0419666230095 +11.7766228743143,6.65669614426539,46.7261486953568 +11.7812624730431,6.46818730109414,45.7447636118561 +17.3411464707371,7.95889571769978,-48.5317771805433 +17.3591374142369,8.06091335402758,-46.9705756839002 +17.4398653541303,8.15159457044852,-46.7718351847322 +17.5178294244943,8.29095038622626,-46.4929265910648 +17.9437033658478,8.34360636109565,-47.2476260210485 +17.7329620522384,8.36656077918918,-46.8636004384049 +18.0945485602069,8.35597857903196,-47.1878389098602 +18.1774066136501,8.31779673050968,-46.7078345412317 +18.1880003919456,8.22555950449855,-46.0651093338584 +12.2048363858557,5.65156192881874,45.9307326204789 +12.2058336366618,5.5903586416188,46.8627669454952 +12.1722172951772,5.50048618358241,47.1247435706968 +12.1479830522612,5.40689886787954,47.4420708439837 +19.3552501601308,8.01898919807975,45.7560127655452 +19.3828890983411,7.9141235006318,47.0779385675392 +19.4108960182855,7.85435975391277,47.5108647746156 +19.4113828554057,7.73263380697979,47.7590978151642 +19.4175226330643,7.65193321941595,48.4433928145581 +19.3434072102751,7.67719691170532,49.4682600823592 +19.3973619587263,7.60051637038582,48.4429140915311 +19.3024688988975,7.93997434210616,48.6224335119368 +19.3073647647605,7.78585973591026,48.8239963884845 +19.2818052265908,8.01311997954063,48.5044723880027 +19.2924244572287,8.11748019362982,48.8963399390882 +13.4994862398755,7.46064602419782,45.3197486506259 +13.3939213731886,7.3060876081679,47.3478287158735 +13.3304544638958,7.29036190114777,47.8315057762997 +13.2458632269875,7.24642138115857,47.250333146233 +13.2122005375035,7.22201146429467,46.2951726352795 +13.1994357536497,7.30791900805299,45.8554952132908 +12.0786769429882,5.42614898669539,46.8063622459 +12.1166811646608,5.41477361173646,47.4203368352885 +11.9004080659844,5.65709962012804,45.1955829706341 +12.1367660996262,5.4263955277898,47.7230211602831 +13.3167917166839,7.6123326776216,46.2696244301002 +13.362816984474,7.62187572153469,48.7702469838687 +11.9849055836459,5.52852336145786,46.414757532314 +19.3467781707368,8.08166666161444,49.3632974875668 +19.4294295264375,7.9071402875866,49.7986829211334 +19.4764061467735,7.71440945729212,49.936137402411 +19.5682165016441,7.44115873194421,48.4425179128118 +19.5844601677459,7.61424827128335,48.2335649494018 +19.5827597206304,7.79093269641913,47.236760637336 +19.5361777892156,7.88877377697681,47.1237982609853 +19.5793103864731,7.8931921420677,46.7957288925209 +19.5909743116795,7.82463879368661,46.5907596665496 +19.579232300384,7.77387644042585,47.549929551305 +19.5660459694283,7.708251966961,47.7706741186276 +14.9726399898406,6.91494771807824,49.9723174023335 +15.1044017139018,6.8840743425596,49.6391414221538 +15.0774796580201,6.93787537372651,48.3677376734572 +15.0256192178014,6.97570815978879,46.6658021791533 +16.4211230670223,8.31932603064782,-46.2429824194332 +16.7814436453716,8.28578229259124,-45.1528049055701 +16.8423251515963,8.34971849678843,-46.9255142861443 +16.8863698832856,8.46203225701305,-47.1396848617315 +16.6592330895501,8.32001675450194,-46.1517368154239 +16.9363140079502,8.48099347664409,-46.9261283872931 +16.9728436840404,8.54205070640653,-47.5709970703621 +17.2247023058309,8.51421478062155,-49.3685635743152 +17.0905315197519,8.50695134273835,-48.5039526114851 +16.5462589705435,8.28786971637547,-46.2732590031136 +13.9528035607885,6.93129255090089,45.3898108318235 +13.8232661429847,6.88616782046533,45.7695315638657 +13.6851978431824,6.83203606918814,46.857113880692 +13.5378256630074,6.80468432731011,47.8559673672851 +13.2992566339393,6.83429456183359,48.6980685397411 +13.1961977175898,6.92704716460462,49.8409449442584 +13.403036178006,6.82737412415684,48.6229996225987 +17.6482199794171,7.86577178804915,-49.940183548563 +17.7230890788671,7.85983264031521,-49.0099511490683 +17.8598596146022,7.88067924841876,-47.0079925740087 +17.9263461441401,7.94041052552438,-47.5777506069779 +17.7793844346428,7.82875506300943,-46.9530699767745 +17.7715364041892,7.81742296943631,-47.8257052189313 +18.1197993204428,8.02739593577105,-48.005982306965 +18.0864736476719,8.07300900873189,-48.6632945956672 +18.0630447854176,7.99964510010599,-47.1486976177963 +13.5713829131771,7.68422337981103,49.0595163947048 +13.7836006088603,7.72345568458824,49.2528378644836 +15.2006234827995,6.55689823121322,49.0310147541483 +15.3002637016029,6.37114015327924,47.2759357819683 +19.5204913507242,7.66862283148341,47.4777136480127 +19.414739314888,7.64625898362399,47.7743420535823 +19.2709240001529,7.67479355959828,47.7234811590828 +19.1599613789724,7.8273050708749,48.3767692792377 +19.0744830787601,7.87698378849857,47.7218734062387 +18.8346604699509,8.2203162459989,46.6462187304313 +18.7379200881257,8.34806955634259,47.0561503513747 +18.5834122337381,8.51216342635155,48.7368041500551 +18.9450639337177,8.02257591999422,47.2049723434905 +15.4426733992284,6.29407734285869,45.188754764466 +15.8974779112183,6.56266048012805,45.0318704537084 +15.9424078578471,6.56784564408109,46.9987818120765 +15.9372541101949,6.47224621706849,46.1629393719584 +15.8951126306389,6.70459892469888,47.1048289275129 +15.8183848654837,6.90157577900577,47.9014368865699 +15.735512604268,7.1794241362849,48.2461440861016 +15.6652474274823,7.47835985477669,49.651833166086 +11.0096700600301,4.96205079042829,45.2437193321874 +15.1409602428608,8.0471908875991,48.3691595934237 +15.2528970580799,8.16603018384035,49.9165863034171 +14.9338868696916,7.80120412044584,47.7547077719712 +15.0247895608232,7.91084995815371,48.2019292445979 +14.8464250391376,7.57943217027616,47.4317984301256 +14.8842269968577,7.65699068027277,47.1897084295271 +14.8449190436775,7.47301842610017,45.9445999574148 +18.0565335353371,8.02186620479068,-47.0616211109309 +18.0454942183579,8.00315052181905,-45.5876969938938 +16.9937400630582,8.23367361173811,-45.9255663753991 +13.0937476352048,5.11454635057714,45.037670882084 +16.7706368180608,7.80302040862109,48.8029330838411 +16.805812511284,7.76857676623538,47.372285805649 +16.8902441598649,7.77226104273242,47.0720093602628 +16.750036470448,7.77395900347331,48.0730668387102 +17.0895778947517,7.85115693228758,47.152980551584 +17.1991051341309,7.92587472803828,47.8368031791802 +16.9774394147259,7.7914159405246,47.3099568223921 +16.876470466508,8.37954068979156,-48.3603362735548 +16.7082661427474,8.56454416657671,-49.8877475318034 +16.1031650106155,8.51349045410671,-45.6628683727253 +14.3061227239471,6.87359171732075,46.3254562226417 +17.4989384081211,7.98255186300642,49.6702787983027 +17.2656830398076,8.02148016155937,47.8355132747624 +17.3307629681777,8.02502417894423,48.284400144489 +17.4279260688114,8.06147473202345,49.4655196336178 +18.7027471349796,7.6497146506765,49.3193520475016 +18.7428360221932,7.68777858407423,48.3454218362978 +16.4183062835381,8.74645971537545,-45.4933236914811 +16.424733619615,8.83588605131084,-47.5224224923763 +16.4790927462296,8.9657813868971,-49.0682452002316 +14.211108347449,6.79470525418656,48.0633932411992 +14.0997586851864,6.62515576864335,49.8578252564934 +14.1351026626778,6.66940831366845,49.6824307524384 +14.0021458082841,6.56120830244384,49.5568935259153 +13.7969285514876,6.74035216688113,49.9024270670889 +18.8120613929246,7.71330241541909,47.59097434172 +18.9158212896351,7.76845304451441,46.9020402216551 +19.0534284759344,7.91203437692403,47.2037066031259 +19.2223193812131,7.88705696284738,45.3429041006371 +19.2414131611372,7.68997353991986,45.159276331145 +19.2904930590828,7.69035833047447,46.7863612698491 +19.3587272396453,7.72441384095325,48.5119292922333 +19.1542114600038,7.88793337481666,45.8307886002042 +16.8683688394387,8.44785118331986,-49.4070399929992 +16.9831265923423,8.4232374551214,-47.9704248847568 +17.0897320812418,8.57271133801867,-47.7399774385573 +17.295286885396,8.63390455930118,-47.4462340482874 +17.1550360983532,8.60942381923208,-47.2729561755254 +17.5222491628465,8.57627636905803,-47.0863925201766 +17.8839782872789,8.50273138584555,-46.7059241151942 +17.7323911634267,8.53946487063009,-46.5537964982528 +17.9999902576304,8.54890529384099,-46.0344189356661 +18.0709997097456,8.63521393061857,-47.0637575350536 +18.0755353532683,9.03288553263668,-48.4964745646232 +18.127283823424,8.94338499460522,-49.7701003655359 +18.1051196009298,8.80367831367666,-47.8044554561032 +18.0778386809338,9.11099504415191,-47.4188819856615 +18.1012604908191,9.15767317060781,-46.3472267389917 +18.2941088791479,9.07107454843291,-45.4128096623993 +18.3004992644396,8.98480647579826,-45.0008394963237 +18.1544676573296,9.11532195545771,-45.1371747629206 +18.2942899923397,8.07572445528495,49.8795583561492 +18.4053743407172,7.78786703084076,49.0942777219132 +18.3481616672013,7.90718123854744,48.8705541737725 +18.4207287983761,7.47377408519868,49.8745651069328 +18.4151367153724,7.38771758322767,49.7022778307631 +18.4288843633142,7.322378349082,48.9741817556709 +18.4586863082895,7.41868859611423,48.5635086699717 +18.4516703687183,7.34289826952763,48.3203418537543 +18.4711111318766,7.42356177833942,48.2601297300234 +18.554694660433,7.40553697379253,47.5042840173451 +18.4040690591165,7.59048310551655,49.1888666994999 +18.3634122538153,8.84925464436365,-45.0507645456827 +18.352917139686,8.84652699890131,-45.7886643872978 +18.2746432082628,8.78102099025461,-45.907143184772 +18.2021620262487,8.77560876282372,-45.8927236377874 +18.6287660853735,7.42313382193345,48.0391563881528 +18.7189579342697,7.4191432846078,49.2096711611248 +18.6805516016116,7.45221728396066,48.7041647167784 +18.5394910680926,7.46813159891467,48.3891755440347 +18.4634430889636,7.48162418698134,48.2727396250011 +18.3480144751635,7.49538994733138,47.879676566957 +18.2170690990386,7.54748097154187,47.4027515235484 +18.6321295682873,7.50307690182917,49.236818033516 +18.0828084614422,7.51161820582154,47.8844557240579 +18.0839361933958,7.50867481382687,49.4087623337123 +18.1193204269166,7.55398719747189,49.1967245200461 +18.158216651988,7.62537703948601,49.3008655794338 +18.2131750329475,7.65264122650153,49.3376063386212 +18.2616826978663,7.68295160507159,49.3023349437263 +18.1296833570278,7.48068870483131,47.5402234608292 +17.3713130281302,9.09403258682508,-45.6551597215279 +17.3023305168649,8.93231419202481,-48.5616583887603 +17.3031732631858,9.02018377938675,-47.1788924829222 +17.3471359613301,8.84265465842023,-49.2860191860163 +17.4193522766008,8.82778437332649,-49.831312625682 +17.5595259851357,8.77200224908922,-49.6792095494583 +17.6985229474969,8.65969796089568,-49.7634655742694 +18.938424105246,8.95115304735764,-49.9830953491539 +19.0873489530821,9.0422482349853,-48.4039411006303 +19.2628708512327,9.02476478930843,-47.1382795323559 +19.2024799505243,9.02871657721184,-46.9446610724068 +19.3464608642335,8.71866777169977,-48.0765325737641 +19.3688160643183,8.48549525858431,-49.1861502295368 +19.3040548862771,8.89174607331653,-47.0498172275148 +20.6830451341882,8.54898665694868,-49.1929184243135 +20.6259433274667,8.60817057347101,-48.5889121390126 +20.6126078613524,8.59847458044026,-46.6982153463752 +20.5940742934908,8.62648825736309,-47.3560195155231 +20.6700798281277,8.48858229714688,-48.4934640646095 +20.6282217479292,8.56288109050851,-47.3626387322861 +20.0751395416197,8.50718684245539,-49.0104663632976 +19.8669160570764,8.55558581589459,-48.3959016784492 +19.7295729804104,8.54160212504755,-47.2354313787309 +19.4513129023078,8.54244881321532,49.9009861175045 +19.4456472250937,8.69928500418264,49.530660474199 +19.4384497452061,8.84260139183296,48.653116284526 +17.486945783059,8.0973822053557,-47.7570445161844 +17.67553204945,8.2653185384493,-46.7573396024686 +17.4015216686593,8.12447040531001,-49.3799401159134 +17.9630631548175,8.67504735973873,-47.7372549248859 +17.8303149123903,8.4553478372397,-47.2029892088451 +18.0719491374937,8.93582144697026,-48.9904053046646 +19.4364938341586,8.85483431701177,48.5324228777968 +19.4284018991084,8.81649721188967,47.4522386450343 +19.3301559731101,8.90245347038846,48.1501875789097 +19.2240244407941,9.04845958517332,49.5142257643495 +19.4295263720763,8.8503060186001,48.0908294417263 +18.0300048934622,8.74286877409976,-49.9008707228517 +18.3803324362494,8.36972927756248,-49.6996241258222 +18.4411252156875,8.31691643191493,-49.6184865521197 +18.4368748451503,8.26913673759319,-49.8296679800578 +18.4128722444867,8.23548762142172,-49.8781882410309 +18.4029338879497,8.19754080379697,-48.3749548987726 +18.3923644348107,8.26115113576644,-47.5170929265142 +18.339836452012,8.37163429331782,-45.0935701899484 +18.3243120251499,8.33923812666706,-46.7237665155409 +18.2295378693216,9.14018697190789,-45.0603532437978 +18.2157736022657,9.09339624839864,-46.5358167712891 +18.2300592880337,8.94528795764209,-47.7382834944693 +18.1797726088415,8.87765825284836,-49.2780820916815 +18.1839104115108,9.15914319046405,-45.8170749480641 +17.8660900608886,8.67620176178071,-47.9608147184355 +17.8910567417341,8.67322997540206,-49.0855579776404 +17.8504086819722,8.69102887766817,-47.3834471002358 +18.0814331867864,8.63672094173329,-46.7904922274857 +17.9382342293084,8.7170194173278,-46.7962660728361 +18.241443761125,8.65952040114496,-46.2131080523935 +18.3092724425454,8.62664041847282,-46.026189368013 +18.4532173087688,8.68077463540065,-46.5036557129417 +18.5984148788006,8.71817093264507,-46.2294529731724 +18.7707898732417,8.71257369839504,-45.5643572801367 +18.7072689918599,8.71382728621965,-45.8339826017064 +18.917451097493,8.50108919156036,-45.2065988113457 +18.7267569180971,8.71402941938541,49.294206661497 +18.8413222301947,8.61457865018495,48.599464823654 +18.8414893265278,8.59788988861583,-45.1153288390905 +18.9322925486319,8.52213622259083,47.3083341395879 +19.7446848677949,8.40749022429378,46.0167638059891 +19.9634636583255,8.28314416310304,48.506702737562 +19.9893150834497,8.20376879041134,47.5857928282588 +20.2090041502561,8.05891949249043,45.8640641131241 +20.0786796946679,8.13004725259279,46.9597780403416 +20.2694550843244,8.03568477526587,45.5926607786243 +19.8691557601885,8.35339855760708,47.4869641703536 +19.0632022791882,8.52977974339317,45.9477392110762 +20.3053629238361,8.10584180658347,46.0784311805238 +20.2926152031659,8.07014472584538,46.501494144925 +20.2906185657402,8.09284673142622,48.4747171424968 +20.1967270664039,7.9633997489298,46.978618700995 +20.2664617523404,7.96177509150858,46.775606608578 +20.09452924831,7.80491249693379,46.0716121884238 +20.1383823700608,7.93048749287108,46.888934768361 +20.0726200675416,7.81219855535634,45.3014240791431 +19.8529827179249,7.56844162727332,46.0376111135537 +19.7922788744166,7.65019253764949,47.9282399752653 +19.7378060542734,7.62463147414867,48.0762552872718 +19.7647122741158,7.63343661004421,47.9859384155121 +19.698873272982,7.65574041187555,47.1673202996758 +19.7650811283697,7.80156705396956,46.3702434323599 +19.7025154563734,7.75191394216365,46.4186597340983 +19.8040690664654,7.98032104422415,48.2953647422797 +19.7882366040744,8.02979223472954,49.222188685413 +19.7111448172639,7.99212235527539,49.295380952651 +19.6208982857437,7.85500444631772,49.2571873326896 +19.5640551940425,7.78303249331845,49.4691333406972 +19.5187811326946,7.72856082359384,49.3685437500828 +19.5734437351048,7.7764044827192,49.0312083117335 +19.7220960962586,7.56890821862278,47.3427699879184 +19.7905813426609,7.97459557241108,47.6898013222347 +19.6894588536956,7.83665779175199,48.5295327274231 +17.5210863081973,8.80665157471835,-45.3609551372392 +17.4613506713621,8.61767027753891,-45.5812866493127 +17.4483150946989,8.52921694774368,-45.1751483591082 +19.8280848786517,7.94520320160613,48.5051118112654 +19.944931907787,8.07431188616193,48.9809829024519 +20.1525335346694,8.31252051678878,49.1775554965279 +20.2966785724971,8.28922727336151,49.5945858279646 +20.3925628074881,8.33303524456084,49.9741113057158 +20.5817761508222,8.20273740605271,49.871591960249 +20.606193693432,8.22196054299602,49.7504636185768 +20.554711797897,8.15845055632157,47.9698875127682 +20.4903177403374,8.08714911564014,45.8707621378556 +20.038143878624,8.20534569806764,49.0096383420646 +13.926157092575,6.70893615495345,-49.2873103547223 +13.9085759992572,6.90012557495007,-47.0655735415798 +13.9087595042712,6.92771992312081,-47.7115348187141 +15.5331265436684,8.23729422494894,49.2629563766313 +19.2903762791429,7.31246676009495,46.7631359623384 +19.3244197571091,7.17657809213506,48.5393108373481 +19.3739438068295,7.16887024702488,49.8822982776211 +19.3714587720828,7.068424671162,48.9008711036316 +19.3643765651648,6.87868277754058,48.3117836545499 +19.2644218730407,6.8148096023058,47.5000872323952 +19.082652051246,6.73061081939708,47.0193321677597 +18.8807573803745,6.76163235250102,48.0438492898224 +19.3664599885834,6.98477570432247,49.287074008409 +13.8909287492757,6.92080995846447,-47.0587377635385 +13.842684516842,6.86097632117247,-46.858252708043 +13.7248152520147,6.79534393034084,-48.829807051452 +13.740335783507,6.73231058938788,-48.8665591192938 +13.7879954618176,6.71590431152382,-49.1538380960769 +13.8184125545172,6.68754537093148,-48.1113599292942 +13.8479777179763,6.64038768457152,-47.6137462309561 +13.8646520306001,6.58499573965992,-45.0954849714787 +13.7807378390428,6.80587091527621,-46.8033079723693 +15.5159350635581,8.14862212543756,45.2295653869779 +15.445591651309,7.9026865875181,45.0884179531038 +13.7368617817107,6.80875785493205,-48.0365583203712 +15.5191364605942,8.23819705341473,46.5555328067396 +15.2840214135212,7.71466120752974,46.155640550514 +15.3596131928437,7.74477262582885,49.2442383488797 +15.3637270683816,7.69098550674743,49.7605590552391 +15.3025079618027,7.72172669220458,47.7660375638941 +15.4008597797774,7.5940260398586,48.1966889213569 +15.4247553035578,7.62778070950118,47.7448805605399 +15.5064637819811,7.73603847238922,47.2398291281048 +15.4391855682032,7.74247405260972,47.8311458786726 +15.5861748114067,7.67750100744609,46.924705646215 +15.6442074844643,7.62173644748541,47.9737369752073 +15.7114784171133,7.61003381103952,49.9041632409516 +15.8015998545894,7.64982492603709,49.9698974829772 +18.5980851104424,6.84021599878126,49.5373906354642 +17.8363622718765,8.38887701044032,48.6670343892611 +17.7992326394631,8.53955657113244,47.1689359990826 +17.8146658497096,8.61251150188633,46.6354164413277 +17.7864363079091,8.68346874842964,47.3971325982746 +17.7432757781457,8.76431287564628,48.8870222354649 +17.8403404591246,8.4702919760762,47.165821296195 +17.7680942799357,7.90951455474702,-47.0571014688455 +17.7177943356429,8.02146987233079,-48.2822383246053 +17.5737898725801,8.44043903319002,-48.3808808828103 +17.4463069998289,8.45057149069859,-49.5156986297607 +17.661343685887,8.23654668416237,-48.9521594803122 +16.6382267312682,9.10348869652376,-45.2404896778208 +16.6844548627818,8.94065521000761,-46.9342424169704 +16.781614984048,8.87482732667135,-48.4493892693016 +16.8649383046646,8.78365246508134,-48.4472497637137 +16.9820237119991,8.7510440252874,-47.6441276580506 +17.1241528743951,8.74603112037668,-47.0821574591403 +17.1899781979752,8.77885088993442,-47.5952360723271 +17.2693989450391,8.72141345250767,-47.9935225692095 +17.059324487723,8.78922701261537,-47.3075648658124 +17.3144459884854,8.72739293238198,-48.4913080367176 +17.4303670888949,8.84976165245011,-48.0526089990499 +17.3775510732938,8.75330060986956,-48.85017999583 +17.4897590726553,9.04940465518556,-48.3446430898628 +13.5614004906366,6.58102924530165,-45.8899030729789 +13.5607087884715,6.69302945670692,-46.5548072350652 +13.5443903673652,6.83529658736981,-46.714500076532 +13.4865454300076,6.84755598500534,-47.0707117419069 +13.503028040886,6.92119532389771,-47.37806064887 +13.5184406037395,6.76097393446426,-47.0069943267135 +13.5536629708423,6.653403719184,-46.9433004867486 +13.5480621998808,6.58459993955998,-46.7442112195475 +13.556869368082,6.55320297971956,-45.4608916653113 +13.5581704479939,6.60653866613842,-45.0312142293033 +13.5451202261542,6.64508994406657,-46.6245405943219 +13.5658243102976,6.74729747412395,-47.9947662877911 +13.5414380215703,6.52265379388761,-45.9773733652177 +17.6008861617714,9.26859000652948,-47.5257200460307 +17.5810950885763,9.16094382654084,-47.7857525505515 +17.5929758497853,9.34955665806617,-48.1211979477195 +17.5769053402834,9.34635965633538,-48.2898450889553 +17.5558510797475,9.33719617199668,-49.7100181714828 +17.3136615448216,7.81681216741183,-49.2602916679947 +17.3496173706781,7.71049100402527,-47.2619030277932 +17.391836050185,7.70343932383006,-45.2861044394655 +17.8933157558402,7.8755884546575,-45.4722649314871 +13.5300639971699,6.80025343359077,-49.3813984071896 +13.5305741935987,6.95117434880871,-47.3363107699331 +13.5467846187178,6.94248871712089,-46.6574934388664 +13.5870005602528,6.97569925254569,-45.4037780601214 +13.6445622268548,6.90105658796047,-45.2214585690027 +13.6250554321082,6.79874059496142,-45.7600299279738 +13.5846561769008,6.67121864095079,-45.3155807870079 +13.6979086143531,6.48036806810165,-45.3691969010093 +13.7475525206384,6.38389274712813,-46.2393680432632 +13.5777585123753,6.66799292412204,-49.0225195824788 +17.9349232397359,7.55977254303624,-45.7933461665168 +17.9428050672783,7.76223086484526,-45.534749572268 +17.9143813758506,7.33154241317108,-46.5938158018352 +17.9077644147077,7.11540053856778,-47.1978424006711 +17.9185815953864,6.91487324675905,-46.5375352096513 +17.962017798614,6.91544757621907,-46.2035083424658 +18.0035715271604,6.94017915391189,-46.8225634233967 +18.0064254685784,6.86553875376057,-46.7283235131762 +18.0077060459518,6.98235895767083,-48.2489173547644 +18.044243542507,6.93073665059687,-49.7538599414137 +18.0220293437274,6.93738980230608,-49.2382739125626 +18.0812686218045,6.70902625757692,-49.1876157418066 +18.0756166284114,6.81500336725408,-49.2574337723958 +17.9450038866001,6.85603325569531,-48.6984947766785 +18.033645388147,6.76531894910165,-48.8035831370893 +13.7660956840296,6.35800311692856,-47.7241273513337 +13.7636930448245,6.37370590264582,-48.8402784450701 +13.754842082277,6.48094071572625,-49.7140078619606 +13.6780627791622,7.21867420360351,-46.4142979818657 +13.7247943023065,7.13078593235518,-48.4778374524114 +13.6288378452306,7.27361405598984,-45.2280553880975 +13.8415610663141,7.14050517135635,-45.734149724029 +17.8431994607647,6.98865569131007,-48.4132696457795 +17.6904044790736,7.34823095888592,-48.7150074178376 +17.664047307629,7.34704571100876,-49.5275159718758 +17.6518363376118,7.4195414699322,-49.3143229315848 +17.6147891681926,7.39945868872203,-48.800625682392 +17.5587937454509,7.41067255126564,-49.3730872798223 +17.4827739048366,7.42420404238021,-49.9567610260358 +17.3682854169263,7.36126899174056,-49.0954693031987 +17.268588409748,7.3216141794405,-49.2005021979965 +17.7314637123477,7.10098148106549,-48.3028928120883 +13.8726692536781,6.77919067493271,-46.4270276653598 +13.9272478444884,6.85991667719353,-47.0782312709308 +13.9436900199372,6.94205092924857,-45.5059834806878 +13.9523705184258,6.90054260576973,-46.1149811655331 +13.2793130437864,6.53874702929769,-45.4040270109288 +13.2595315302188,6.65117991071255,-49.8310343339318 +13.1594896082732,6.83025395558047,-49.6298474369652 +13.1136783998614,6.91457559998541,-49.0216601786681 +13.0699850213405,6.89993734498591,-48.6364639111577 +13.036860017134,6.95714403338402,-46.2366177114567 +17.5959840703488,7.21955305934902,46.4000147516933 +17.4235803585256,7.24649788182226,48.0688351431799 +17.2650062353292,7.36993535271526,49.7619041035101 +16.0015092911099,7.92733497492095,49.4989327784588 +16.0060507858847,7.90011002713081,49.8981830375057 +16.0510699762215,7.75617103605648,48.3288812938511 +16.1029836719844,7.63783416062318,46.0811146525677 +15.8724640473756,7.95725220078284,45.7737338644936 +15.9443845079002,7.94317509244408,45.5784463340871 +15.7828387492281,7.93434047124489,48.1346824245826 +15.6554712711298,7.87666785987912,49.3314073114966 +14.9122209198417,7.15120944063252,49.1601143726864 +14.7882950020143,7.22515471736643,48.3338536348439 +14.6682853382927,7.23876744519547,48.4029358402607 +14.5505947124376,7.23280027772352,47.7841585199374 +14.463037771432,7.26619647855715,48.9801085172471 +14.3798172118085,7.22900478859246,48.7616873749117 +14.2963373222812,7.15917059092514,48.9368047133406 +14.1731654603999,7.24002725927017,49.3002065727606 +14.0957197330562,7.23345391419191,48.9126519850427 +16.2304541485362,7.86340002188243,45.1787802372955 +16.2558880772964,7.67860297526834,46.0382638058009 +16.2298592338018,7.55839044217806,46.8064125244873 +16.2121187010322,7.46692543438765,47.2376770735884 +16.1857812990423,7.39495865191327,46.7106342478659 +16.1280004381272,7.32370706153103,45.3379485783612 +12.9594094299983,6.96371618808112,-45.1109950609981 +12.9155726031099,6.98251705979326,-46.0115961420493 +12.783698963589,6.92636180251527,-45.9629848279465 +12.7244776604588,6.99698525107427,-46.5493982762722 +12.8560745347149,6.9091881296693,-46.177463683781 +15.5066224249892,6.41108043860979,-46.9873410125302 +15.3905613656513,6.5199887170081,-46.0766587805912 +15.3351664031136,6.73959284103891,-48.7799886654948 +12.3698777153117,6.63048612634977,-45.8531128462444 +12.3620157497563,6.7083886725118,-46.4311935863392 +12.3978122024542,6.71998214459588,-45.2289560922058 +12.3791880492284,6.80996656612482,-47.8726841189188 +12.370909573388,6.90109177848467,-48.6025087395191 +12.4105139291896,6.95880086245381,-48.5996667158624 +12.4642914357848,6.93365640071674,-48.5807635893164 +12.5056278641439,6.95538289227499,-48.2879638464626 +12.7039941193367,7.02960810502648,-47.9155935635992 +12.7458337594025,7.0526554479729,-48.7941771250877 +12.3640463480898,6.77275065639489,-47.7279529517747 +12.6141148033945,6.95650176323121,-47.6790200139318 +12.8739081860113,7.01469546189737,-48.982654061624 +13.0038043913697,6.99822470550092,-47.8235834494394 +13.1030244949295,6.8803856386726,-47.5329879619639 +13.1879217278782,6.71652509682011,-49.1836681125621 +13.2996345454944,6.60264027582608,-47.6635727197934 +13.4515562050239,6.45520855231185,-47.7514661904033 +13.6190725551461,6.49446440917999,-46.7927377792828 +13.7429089228507,6.54575673628467,-45.8866101584863 +13.8184181727227,6.57276313481217,-45.9605555568383 +13.9396383244933,6.51100019468968,-45.431778865501 +13.4008450427882,6.52513006912348,-48.808125937145 +13.5133849467195,6.48233504862115,-47.9520774919372 +14.233041977107,7.65335745135447,-48.4493511710077 +14.2468820214802,7.78292741181967,-48.7602502097585 +14.1847076710866,7.75665484908544,-45.82158370876 +14.159117662151,7.76506550412052,-46.4233197234004 +14.1363873522022,7.51972236997398,-45.8917655266787 +14.1593758307463,7.36414323390055,-45.1674312234829 +14.2202507472276,7.81257281754817,-47.5619253915725 +16.8692152615572,8.61906826843499,47.0120060918669 +16.9422908952242,8.65022999960768,48.9151133685082 +16.8146737696352,8.19355757204284,49.6832624914894 +16.7028399660798,8.08641251351463,49.7463342460335 +16.6748267517244,8.07284001234727,49.9501992638355 +16.768968715681,8.14563353894926,49.3966121080907 +16.7974466690402,8.17383236711935,49.4617723312622 +14.6216548990942,7.28742434766692,-45.4834053344417 +14.6936535140874,7.18205159652689,-46.5070123560371 +14.7391336732256,7.08255276547784,-47.6766791882544 +14.7514448252809,6.91823377010922,-47.4371197492072 +14.7052147182471,6.78429202012893,-47.1146782009901 +14.6936638598153,6.71700848446034,-46.139482508616 +14.6752617772099,6.72652139432068,-45.6584437585627 +14.7128661994205,6.91311197006129,-45.0474705104771 +16.3323853062478,8.19018025143832,48.4333315877149 +16.4891337890477,8.35028707753382,48.1974875917632 +16.6718541582676,8.46652220603516,48.0773978131159 +16.7912055372361,8.52678676978176,48.1677073769812 +16.2631323684003,8.03133374604655,49.6594811357653 +16.9154231265457,8.5681588580383,49.2128009655919 +13.7891270113058,6.50194769783947,-45.0392689267005 +13.4642194126794,6.41684183624225,-47.5619111486237 +13.3727244395633,6.55517563920605,-48.8487906513916 +13.3620710265496,6.57851349563745,-48.455517251457 +13.4115091656009,6.69890744664686,-49.1904389768634 +13.4081312047058,6.48800757565664,-48.1740414278031 +13.0672304682646,6.89490013688092,-49.7484918944952 +13.4045520481013,6.7244011727616,-49.1580586357957 +13.6320632040366,6.46095930708491,-46.3760534033041 +13.0455094848644,7.08277446614797,-49.5874902894748 +12.8675644083697,7.16481671974053,-49.2827450907647 +12.8412697761289,7.04210793905529,-46.6383377794481 +12.807028886585,7.03880919012617,-45.2133917744371 +12.7168741076527,6.95773208125489,-46.452863522733 +12.6127639022836,6.89625939271712,-47.1881663911253 +12.5999091664179,6.87557643971049,-47.9596433702084 +12.5890127688057,6.76020722369714,-47.1014186282685 +12.4913036481833,6.73609301411406,-47.6944401613566 +12.4992201196641,6.74986703199699,-46.547029955391 +12.873703361354,7.0856366393657,-47.5806186566718 +17.5614017167915,8.32182833896999,49.9250066801784 +17.6843557676289,8.29113888451407,48.1739186531069 +17.633526853314,8.28536299623135,49.011153988029 +17.7165750074242,8.33982327724067,48.2438217053857 +17.6561568336547,8.28688692978775,48.0062240561623 +17.6341733692092,8.26478715868332,48.7007411078034 +17.6005148717319,8.2077949675094,48.7708437245656 +17.5596794548423,8.18871666702323,49.1507830910689 +17.5139285692871,8.22374185916857,49.7006061181176 +12.6704481356988,6.85744623891602,-45.41585868106 +12.5366825685832,6.81604214036165,-46.4305030495947 +12.5847944437019,6.71109353382398,47.3766993401997 +12.5114662433541,6.61144708270231,45.3382996162193 +16.950654649008,8.23225629747089,49.3891052578856 +16.5522994339901,8.29423778009925,48.9740719163539 +16.6867949446373,8.20879441086708,48.4562615214036 +16.7938627624992,8.19085515056919,49.0933260627233 +16.4366779426309,8.37356294636888,49.329164779075 +13.7279830475042,6.56781082856062,49.5132382361098 +13.9481810400353,6.56930948458215,49.7430986637638 +14.0211166672676,6.57510089230152,49.263828673367 +14.1126765915659,6.83432845417788,48.8941893928715 +14.0557695732069,6.64127504166366,48.2637627430261 +14.2558583268269,7.43168800527063,49.216384405241 +14.305917346442,7.31523542864841,49.9846239886074 +14.3779445358742,7.16945740379471,45.5394152764296 +14.4592808656368,7.11259229726975,45.5198570568959 +14.3175815854634,7.23588635157717,46.1461136979214 +17.3688350878205,7.03636378077842,-46.7005187598211 +17.2248714584084,7.10623366430854,-47.1520778365879 +17.0212744757817,7.26126456547475,-45.7240716181198 +16.9148332204983,7.36577429392771,-47.7584811751131 +16.7956817597007,7.45637951541622,-47.8791360389937 +16.6960219979393,7.55977661657329,-46.6879368096634 +16.6062789663101,7.61300986138605,-47.0457524010685 +16.5747901454182,7.77467093910565,-48.2786893080581 +16.4173176419813,7.6285083562601,-46.1730968855037 +16.5542097087055,7.72283265515739,-45.9441431160737 +16.4052924640797,7.6449171847137,-46.8213885291551 +16.3909873395059,7.55261136655049,-47.2907295402291 +16.3136482220365,7.61840377298727,-49.7581374371332 +15.4192710453836,7.86507121948245,-48.8002640965989 +15.4627424230697,7.78423396671586,-49.2604702539203 +15.533479339939,7.68774032341948,-49.7098300694553 +13.6690888724199,7.8589031121039,46.6743495243905 +13.7144523676232,7.94394049784841,48.6284233445577 +13.6009400001938,7.85822632641592,45.7535139698995 +13.5472984200375,7.9066405108209,45.2057612599436 +13.5100935513724,7.99622959135375,45.307514313569 +13.4208772549987,8.11035691793858,46.7564953683947 +13.3061350424582,8.14684799222996,48.2391275407735 +13.2919282848404,8.11793785521374,48.4567505901979 +13.1832993936024,8.07495428484729,48.5859511008077 +13.1165151512085,8.12101004067327,49.598550754122 +15.0547296771115,7.17991523869953,49.7814340160006 +13.3274422253293,8.14296218791402,48.0330094962744 +16.1766539340944,7.49241113316009,-49.966167598013 +16.4958265598547,7.28344874049037,-49.7140129166739 +16.5204854116385,7.34537257208655,-49.2295392710746 +16.5386832550128,7.42444109454994,-49.5100953564062 +15.599383207303,7.65751436199879,46.9352157956134 +15.624312239438,7.55140493120937,46.2263140220403 +15.5075964417773,7.40100890396584,48.436373652399 +15.5067392711457,7.30789093754317,49.3176432772098 +15.5725329447865,7.38411517776336,47.8716776995431 +12.9341724297802,8.22695575641199,49.9786305035335 +12.7049047105472,8.57089463993075,49.7609266703979 +12.6436382618827,8.59508287479697,48.4232256661804 +12.684220663375,8.57723449445241,48.4569362010982 +12.746222178316,8.78452468339953,48.0915307499616 +12.684421569063,8.65280274926365,47.6281487003382 +12.758144174972,8.8854337445565,48.6228477162566 +12.7317544122814,8.71420769644135,47.3661735565674 +14.1811533257755,7.41007424885582,45.610340333492 +16.6950169002715,7.72869755443771,-49.1241047690003 +16.757355395358,7.55080702389279,-47.7020947141293 +16.7333255038826,7.66956165629531,-47.9040176022312 +13.4951899912443,8.93135349995986,48.7935350943636 +13.4420351322745,8.92659042838206,49.6855615417704 +13.5391892402537,9.04908314786618,48.4907045019221 +13.6527862553642,9.14975989166657,47.789753086496 +14.0198111469026,9.58817268073603,46.2757524023363 +13.7741789192605,9.24172895627947,46.9847645591983 +13.91063237292,9.43986418284113,46.6373828177136 +14.3466881741529,9.68434833761781,47.4693088333939 +14.5212324448064,9.54504483951725,49.0527158521244 +14.1576775247692,9.64796446004322,46.3542551574384 +14.304708463512,7.74368378161019,45.9406949453995 +14.2955855296856,7.80646865073353,46.6084436035427 +14.2578981383427,7.87302362502089,46.6833788512088 +14.3244151417105,7.90175995097902,46.6614730487278 +14.4679696574759,7.81348078569503,46.1349843420848 +14.6587543602326,7.82265585361191,46.2413866998488 +14.1751318479207,7.54319962368422,45.4175923741338 +15.1148978482962,7.87461762288197,47.5719299581417 +14.8732395734429,7.8142155973433,46.9434779680263 +14.2603888609934,7.90121468292484,46.5233185120863 +15.3565116101519,7.95692117007369,47.681360518315 +15.5588901745954,7.97081947792359,47.2329158393418 +15.9609474926025,7.92734896479658,47.0543413313904 +15.773628738844,7.99525540472982,47.2321254819503 +16.5610352347631,7.60774211683619,46.3970742078435 +16.2014696161705,7.78610770211345,47.0323803740502 +16.7927332971676,7.60155328771026,46.6527507538767 +16.440519160284,7.6139863932475,46.8405057814398 +16.0941361954529,7.88317735552206,47.0426883851854 +16.3355190566511,7.66215695757393,46.3362901299678 +17.0083880716543,7.56749794884895,46.7313007763476 +17.905613049808,8.3460898806339,47.912650030787 +17.8118609349302,8.21441123166618,46.2919812176637 +18.2035272489819,8.4746668040328,48.6301603051605 +18.4016782527411,8.41018844543088,48.8592381262682 +18.4791208449062,8.17758456293748,48.8471299512042 +18.0416506846533,8.36426282418556,48.0142377871991 +18.4907666342555,7.94909046176193,48.8496357207218 +18.2925367544897,8.45600927268615,49.1191825934012 +18.7263489803297,7.75994706593364,47.9607596830591 +18.9454909269652,7.68087673465786,47.9616131773477 +18.5605028132126,7.83192193965617,48.2870526083381 +18.9962944615212,7.59072412453387,47.4145413438679 +18.8689519899389,7.63695913990879,47.7881736713362 +14.7925317704553,8.64424249748003,49.9735691531075 +14.9657466935277,8.38539653240813,47.3182289274567 +14.8538225543108,8.47853706097872,48.9774279753196 +15.0941936707315,8.30749303811069,45.8145841350515 +15.2470490335955,8.27048253325896,47.5515937630684 +15.1924803630864,8.2466358718862,45.9504664753441 +15.3260929811442,8.29455557625997,49.3801579694779 +19.1128656091056,7.72541526005789,46.5419491449769 +19.0527120304656,7.65769958509514,46.8332172198379 +19.2420371220693,7.79851045340432,46.0698711828238 +19.1727181774545,7.76056003827119,46.5459605097337 +15.3726950986628,8.50378095840706,45.2290121158227 +15.156949584709,8.66747702098843,46.5655408161358 +15.2688728625391,8.60948713746638,46.7433670007192 +15.0191074139629,8.86116552375776,46.9780916329492 +15.0718225477357,8.76041271159272,46.9399159869187 +14.9745346978526,8.9767571870699,46.9461508560754 +14.9984930162202,8.92075286238736,46.6732919705107 +14.9590052044202,8.94899795405876,45.9317766782439 +14.977120653822,8.92218047565842,46.5581041481602 +15.0019023317603,8.72227392548389,45.7079937102098 +15.0776399636454,8.47930305779548,45.1455530716694 +15.043800940989,8.55608245462002,45.1662057449208 +15.1403052322332,8.27759278375683,45.3497107718201 +15.1245674676123,8.29441928407851,46.1198875034926 +15.130487850316,8.2114785332785,45.3235206550815 +15.163696785664,8.31877835005214,48.678115910134 +15.1430776344355,8.35238934920518,47.5114302060284 +15.3077445233218,7.6724399789973,46.8489676488108 +15.1860707930321,7.86285256303862,49.7619989114521 +15.7566223184577,8.31535834671703,46.024315898236 +15.8152174324297,8.43333847055423,47.764894071947 +15.9101713495037,8.52059847838835,48.3316484154075 +16.0249182791046,8.54631596466466,47.7633618862152 +16.1540787455336,8.51977788871682,47.0318776156474 +16.2675848346523,8.49866876898398,46.2714703118816 +16.4249360058936,8.44481333386864,45.9705450129828 +16.5539569995384,8.35638621757327,45.8435638875063 +15.4621608882941,7.63849553956767,45.4030943272941 +16.6304956016212,8.17276153972744,45.967839682927 +16.6099387797383,8.27033402097494,45.7207804062088 +16.6265033461852,8.18003361007564,46.5967128530094 +16.5728073791859,8.26139230915308,46.4537466739321 +16.5657215813554,8.2555175571146,47.3688007261126 +16.7435218510442,8.30114183331066,45.5003174056487 +17.4509079376825,8.48960118168204,47.5974265458507 +17.4563672939514,8.41299212229013,45.8878978621165 +17.5144674222488,8.64689700720408,49.0283956870867 +19.7814584478705,7.31976051023301,46.0259373359573 +19.7488681899079,7.33762705319192,46.5861023253596 +19.7638880297812,7.42766007164207,47.1638795235526 +19.7639920434235,7.51482008699829,47.2046092933089 +19.82227278249,7.64293951394597,47.1658099961676 +19.9458124929727,7.88690928025005,47.5068019585462 +19.89160438888,7.86351143044417,47.7783447533185 +19.9194845114042,7.94708817100037,48.0093339413405 +19.8771302257017,7.97130030259932,47.9010767115403 +19.9608158916061,7.88311925669708,46.462482745607 +19.8811854261873,7.93130970979825,47.4815222131526 +20.0020508093636,7.66661886577619,45.0972210716845 +20.0001432028821,7.80050567031144,45.8941223179274 +19.7570912406096,6.84449681522698,46.6119443329124 +19.6371964586688,6.77977613566188,47.8200562730003 +19.5359753726224,6.82763391369426,49.1608517190749 +19.5183938598262,6.86133858642088,49.8826441506772 +19.8329548809305,6.94276795139937,45.5564430911369 +19.4925909294847,7.32786178057561,49.2104070941505 +19.4585692655574,7.36435164942922,48.6795643996883 +19.4189786022534,7.41866082493238,47.0644645282881 +19.3702163297086,7.456556203825,47.0183807343762 +17.7117077817512,7.44144307719526,48.0245032267089 +18.5387904472708,7.49657401943843,49.1469660545208 +18.7810818015076,7.36738730685962,49.8463220535799 +18.7548281684465,7.3402388143153,48.0199297785666 +20.3168458459434,7.64123868620004,45.5682723651726 +20.4882998457474,7.53056541304519,45.2288904903288 +20.643503995666,7.51373993015684,45.4141958648016 +17.6306298149447,7.86599599697992,-45.5396289043478 +17.7113284274037,7.8940443585704,-46.7630297073853 +21.2693220425515,7.4086669857909,47.7812079715645 +21.3665961376939,7.45220344367702,46.6557723002715 +20.7697188608777,7.05818404084831,49.5213598542658 +20.6121188479576,6.98560704626578,46.9613215355972 +19.7865420137806,7.7183293577925,46.1379874537375 +19.7432435006464,7.82968720753443,46.8198408117728 +19.7144533569752,7.92706095390121,47.7728125892643 +19.8061965921215,8.25175632709749,49.1032593037255 +19.8195818953727,8.2446991032983,49.496031340484 +19.8440682725962,8.22267055163503,49.8249584347826 +19.7693743739314,8.02621530129232,47.7245327650951 +19.7964781595429,8.17989830863012,48.4789582922292 +17.9756140519964,7.96946965918505,-47.6897395535902 +17.8874849869505,7.92612971218388,-47.8698631911884 +18.0231437329838,7.92735613443898,-48.6661376973642 +18.0607625741855,7.94344328601665,-49.4996518416173 +18.1193886220118,7.92078957386766,-49.2500153424101 +17.8178986694224,7.93088370619159,-47.6667960625634 +20.0302062702573,7.82168212514985,49.7359391176233 +20.1294193720289,7.58861055391212,47.9584858004114 +20.194098146121,7.3684868566802,46.6766745304376 +20.2415070954147,7.17094656842939,45.3136983712276 +18.1201657356985,7.9446041401321,-49.38574402331 +20.3524107820766,7.04865910064385,47.2672137201052 +20.3118973553754,6.96328212112571,45.0014603756619 +20.4147837091013,7.12402764869511,49.2971312427499 +16.2520671536896,8.56093950972476,-45.5051342805279 +16.324290597955,8.52547504790432,-45.7750033807793 +16.448673924303,8.5190371800344,-46.1228516193555 +16.5934878594504,8.4872720835429,-46.978582757038 +16.677032460897,8.38475895640298,-47.5555828401351 +16.7959590443714,8.27929202277663,-48.2338614094225 +16.9484839062865,8.17303320541638,-47.620911428934 +17.0730923449124,8.11885835809796,-47.9034361599378 +17.1483566318596,8.02838016158896,-47.418564725856 +17.1961560533842,7.92317967676666,-45.8443938177967 +20.1247738144838,7.34734872886735,49.8723434611999 +20.2463664080727,7.84316217157266,49.7979175036581 +20.2086526460952,7.71694127289227,48.7652121449657 +20.1825695608552,7.53244496541323,46.9177543963748 +20.2077315219936,7.3847671658329,46.1868984569272 +20.2034147976273,7.29611024408292,45.659007563229 +20.2342368194958,7.26892125458884,45.7134939280649 +17.2321556058816,8.13258550941831,45.2888673983759 +20.4497768680937,7.47368880474023,47.376512728576 +20.3427961158136,7.40834011243024,47.1053360254353 +20.2567638843343,7.35566824994271,46.4504224519254 +20.7528853310772,7.58849590409821,45.2958362147274 +20.6215267670374,7.53213002939957,46.7488110324328 +21.0860455346715,7.11225728306615,47.0756439943294 +21.2176322157415,7.11565816019746,45.7253507567766 +20.9474666437608,7.02066454655388,46.8673529593999 +20.4358705355982,7.11289278744402,48.4997259380071 +20.7413811360954,7.05514267235887,47.7411522861345 +20.1971255852696,7.15634059241934,48.2109480925336 +20.0587456458167,7.3207976878673,49.0030710690726 +19.9223462786908,7.42470733333528,48.7333159432811 +19.8495709428933,7.48216003339761,47.9275213275702 +19.7545662593236,7.57094278853002,47.8445754262391 +19.6176563327783,7.48091129323772,46.9981983508645 +19.492985324527,7.46495436589543,46.864163416565 +19.4678703563967,7.27140383009331,46.0946665494575 +19.542471701966,7.14490879537054,45.428803986443 +19.5779433812959,7.20685791946412,45.1897408294975 +19.5925540199748,7.27802591404931,45.391463189531 +21.4115192317991,7.25612571825898,45.8383464979793 +21.4036403781153,7.29069312343371,47.1185853867674 +21.4094666510609,7.23183757218546,46.1543890876962 +21.367184830453,7.13268477127181,46.1840201841436 +21.2848342411055,7.12352920370299,46.353152872748 +16.5968942634955,8.83563531059608,-45.3305793612354 +16.506054574774,8.57611145416163,-45.4637336970005 +16.3934787669429,8.3982387597811,-45.8311641765281 +16.2786283695344,8.20611482763686,-45.2084734801104 +16.1137924713872,8.1721853543775,-45.3659402652519 +15.9492316732801,8.07658152386244,-45.1681153953887 +15.8779357622023,7.9927900396084,-45.5021674505716 +19.2602549421018,7.23040326082391,-46.1014705288194 +19.2516006697489,7.56081169661104,-46.9666025120635 +19.2128582515622,7.79565546567494,-46.4153882447974 +15.8494653575168,7.94604782933593,-46.4742969053544 +15.7647790184065,7.78954921701184,-48.781722994464 +15.8145952629521,7.90817188073339,-48.4223443655428 +19.1796426194469,7.83542241159716,-46.5350734054245 +18.679768857373,7.95541610404532,-49.2705902506827 +18.8213889534999,7.97245786318992,-45.8892645194052 +18.1982523672966,7.88239691940921,-48.8562081382538 +18.1957890632884,7.84376628993059,-49.8972512856948 +18.2341876703194,8.31958935708893,-49.4320532242719 +18.2042844732348,8.14394119768155,-48.4679445284859 +15.2072350007696,7.15123213479733,-46.3942055595858 +15.1763867785061,7.13726455834651,-46.0084674176988 +15.2573708324892,7.2492229237258,-49.213001304035 +18.1624513262515,8.49532832565512,-49.4001468186996 +18.2176078744535,8.45820950904031,-49.8748670933032 +18.1118527345623,8.50737352089133,-49.4610199735933 +14.8696710609872,7.49621773011731,-45.3214382449169 +14.879746163571,7.36233667181658,-45.2760511870895 +14.8461532605977,7.70874298649035,-48.3803519547932 +14.8463323267648,7.66481716138287,-46.672388794495 +14.8303824566052,7.755236483207,-48.8773916383559 +14.2893429739648,7.81935069737247,-49.9299872399318 +14.1674825979201,7.91504313681742,-49.6826705001341 +14.2158064985425,7.91899591833758,-49.6827865371214 +17.4969211083757,9.04682183859159,-49.7359361391279 +17.4747872363913,8.9540581851569,-47.4924097468816 +17.4386676738794,8.87005221216993,-46.7994480849455 +17.4092249159367,8.80431061363667,-46.7907035497027 +17.3142321212138,8.8078359406552,-46.9503324916957 +17.3645429349553,8.7981396661959,-47.1373494915691 +17.2501395644697,8.78975797214873,-46.6599085810977 +17.0407051794525,8.45858483832092,-46.8570325496291 +17.0472586800398,8.37113925332731,-46.6782954758667 +17.0376085816151,8.14926897035699,-46.8326807207309 +17.1596149253604,8.7332113604285,-46.7227391086046 +17.0817466936277,8.6164147569006,-46.3254244550928 +14.1372006329758,7.86086395670876,-49.0571836617846 +14.1452701535132,7.8141166879202,-48.6477533260746 +14.1458040960607,7.81414031138345,-49.3276324727149 +14.1694636276639,7.76366501631255,-49.1229782706363 +14.1850068854069,7.75956538457967,-49.0280335235722 +14.2386778890438,7.8032726839152,-48.9127957492152 +14.2967207608866,7.87746186889546,-48.9491721256886 +14.3183696710424,7.89875636836416,-49.2047034537278 +14.3627546785722,7.9108798744865,-48.939794137523 +14.4097143475801,7.92042372019386,-49.2387835779647 +17.075592130947,8.10133009286078,-48.2434462558405 +17.1850075049293,7.9836765503371,-48.8083808534452 +14.6970616106944,8.37766380593159,-49.7964631256935 +17.4973181632141,8.39688257889096,-48.8668280731024 +17.4229624972084,8.39944765662387,-48.6411691424621 +17.3531122787689,8.3321576160324,-47.8829010911019 +17.2998800817551,8.28157425446517,-48.0657815481726 +17.2331252638007,8.22974084717225,-48.2939608619963 +17.2341565102757,8.21509827363464,-49.5896186838454 +16.9768977944385,8.61110618064858,-49.6654020343891 +16.9182721179584,8.68823286083148,-49.2142374961765 +16.8109903929583,8.76690345488286,-49.0859311005936 +16.2882846640736,8.48539931437312,-49.5471281192838 +16.3233267326357,8.59105480164652,-49.7896426349971 +16.4846229288853,7.95842162507354,-49.5604016469793 +16.3607679998446,8.34662315314322,-49.9668212298912 +16.4476023175335,8.14259682467893,-49.4013146362863 +17.7013215564209,7.9328199296337,45.4495614193954 +17.6425625773511,7.89129376045762,46.2286854443379 +17.5599984690602,7.86069372238443,46.5433733586679 +17.5181399093528,7.87198666755493,47.1253786406907 +17.5363891066321,7.92439159279897,46.9862869728402 +17.620479467353,8.04564142440096,46.5431400145968 +17.6157997116886,8.13441557703474,46.7316414793829 +17.6113685701775,8.25023002653554,45.7579584003047 +17.5756564155268,8.20233087345562,46.0004432709798 +17.5074322990612,7.97693646292636,46.6182742427235 +17.42528623428,7.83018994686919,47.7380787818259 +17.326786951207,7.72215025574387,46.7368370897827 +17.215848867638,7.68359113518314,47.6576040660313 +17.1301325395837,7.67761259834354,48.8132334358937 +17.0587585970588,7.67790565999985,48.558486548127 +17.0273601652719,7.76470262133258,48.3381445569828 +17.0315518645696,7.81338238044216,48.8252155462496 +17.0607459791108,7.86777885108601,49.5008101746657 +17.0068639945122,7.93543457584614,49.7031125102807 +16.9511801669056,8.07490521278251,49.6153200230658 +16.969213645182,8.25060489868474,49.1730643511589 +16.9437795216044,8.37730961931278,48.4916752478909 +16.9567834446897,8.55242730968029,46.9821860349858 +16.9594962151661,8.64101076133567,46.6957340138393 +16.9315609547489,8.6113436426989,46.6622918479819 +16.9361533458992,8.655720446172,47.2566667568859 +17.0032786114397,8.60662745673471,47.4650237278993 +17.1295384856328,8.59554385935064,48.9360775997197 +19.5908641755298,8.07974077545023,48.4654493221612 +19.4906302803054,8.12919104582299,46.1098633397453 +19.3327495747185,8.13710329764902,46.264770975426 +19.3210750688079,8.15663918549569,46.4005696644762 +19.3949520929594,8.14804856020208,46.0553306919753 +19.3055237964413,8.19221586403366,46.660254647215 +19.5503341346981,8.14407943894035,47.4874717907039 +17.9020977560797,7.89445241481709,49.0614499783807 +17.9606914623818,7.84534340990955,48.6081522138812 +17.9342336315064,7.75962477122671,48.4815901132601 +17.9096118880652,7.79741104009557,47.4650534832266 +17.8720776257226,7.71281530095617,47.3244522382981 +17.6850820572812,7.70388030742861,45.3080405324433 +17.6433924039645,7.71799031157537,46.2804263069817 +17.5882552996493,7.81811626991343,47.487843259996 +17.5183369326132,7.82242939779308,48.2681048697178 +17.4441185838372,7.97065989340537,49.798038652539 +19.3326903329462,8.60453581038987,45.2774444206824 +19.4253826532977,8.86127552787855,45.1089192465229 +19.4103810449009,8.8674873218932,45.3078429318206 +19.4008459610969,8.15250412628558,45.1355474857203 +19.4005207754725,8.21795256171364,45.4413182471705 +19.377657185336,8.14932550281763,45.2744200321485 +19.3084296722361,8.40661466760268,46.1391089911759 +17.1027714541269,8.24046475682604,48.1331618518997 +17.0925041342527,8.22114630383465,45.0922684294549 +19.2267589566891,8.35520553304029,45.0193737591287 +19.1868982875906,8.48683492254958,46.2547642356587 +19.1583700816178,8.66686454938147,46.1030981445219 +19.1375694314726,8.60922690374661,46.4392222779001 +19.3417185714027,8.36523617977846,45.0340505950836 +19.5115570862135,8.5067380460919,46.949638505207 +19.6143392058396,8.53692472308739,47.7049868314281 +19.7094394866205,8.51740695325418,48.0261531423575 +19.8167024718246,8.51014791120583,46.9408734635326 +19.9708580829315,8.64061304794408,45.7880173095812 +19.9189555608791,8.54441208713535,45.9421799414827 +20.0869535383573,8.80873140793627,47.1975579407301 +20.1397933384902,8.75987471621679,47.7809802273769 +20.0879733062474,8.72601886719167,48.3834912564432 +20.0281300163989,8.78830726583926,46.4454355300492 +20.0144445232167,8.62796103419145,48.9340997028126 +19.8802822913904,8.3246513453419,48.0927049313937 +19.7862705139285,8.31421051367449,47.9501445757891 +19.634302065621,8.39049166825174,48.1563844159583 +19.5196281828781,8.50197502644344,49.0210301808689 +19.4172791610015,8.65418518952319,49.6791400517724 +19.9407755700106,8.47896703021728,48.6410273752883 +19.2860941778694,8.73369792609344,49.3927915688075 +18.5630268516,8.40561348607413,49.6909417768705 +18.3930551635992,8.31758853102135,49.0834627826129 +18.2422342302753,8.28473018579584,48.1298066775769 +19.0201733316701,8.84118414935412,49.9701182589524 +18.0049886541337,8.48258496891387,46.1051177403361 +17.9280909084513,8.66742372417101,46.3490683921879 +18.1206610064303,8.35146966728693,47.3304539237135 +17.8416538597908,8.8469252904692,46.8798132317443 +17.7563733453272,8.93918414879897,47.8115466734844 +17.8103928864119,8.95664837856676,47.760134290784 +17.728040951533,8.94431740787822,48.4184047716105 +17.7123382625997,8.97201599283645,48.636351948785 +17.6578880514861,8.9724998465502,49.6215712736409 +17.5820864574796,8.93545880295558,49.9426842346905 +17.5217041936088,8.96704972649697,49.4098650359639 +17.5123680095058,8.88531218804846,48.8916101300449 +17.5068552534587,8.71287947748668,46.8752189173451 +17.8125201091388,8.96012185328471,48.0410786137428 +18.3892868598887,8.9358796786589,46.1503609665397 +18.4263803638955,8.84564243569158,46.3904363936855 +18.4268187161929,8.49110768084147,47.3068030664799 +18.4469223671404,8.654631368119,46.9534814558466 +18.4314758589926,8.12466658093045,46.9294110070801 +18.430348489073,8.24188245101749,46.8289674675252 +18.4700720723606,7.95606508632436,46.3500993634638 +18.4511814158941,8.02053985783505,46.4126145625178 +18.4929303525402,7.90732495609957,45.9133850278001 +15.9047106308368,7.41241681823723,46.8307983047812 +15.9320731082364,7.63274409453709,45.9252579412715 +15.9203421711462,7.51447607625344,48.5407213807324 +15.9553926593647,7.70956572976893,45.493130982992 +17.4983383276402,8.56609050178517,46.0411096900455 +17.5482434530001,8.40586035722209,45.7625637696895 +17.5184617398535,8.48806448009352,46.1866425970457 +17.5828817003581,8.37760008620629,46.4903994524885 +17.6267347772677,8.4433285793018,48.2788168208768 +17.6473811416296,8.43016488675633,47.5739657565207 +17.6326242010955,8.41512660071346,47.2359109256242 +17.635030385616,8.30393306063929,46.5126016419044 +17.6354772631968,8.34703651465074,47.4102140598912 +17.573171027511,8.45787780777615,46.6658018243044 +17.6103975783799,8.40798955068983,46.9094714187175 +17.633282097787,8.46243477576846,47.9124635169651 +17.5509614908523,8.48403169677404,46.0097105013177 +17.632991138282,8.41450978530373,48.5547947995971 +17.5814628991565,8.47008870055168,46.2099958733053 +18.4747149755585,7.97954821235736,45.3363767358545 +18.4831129834708,8.01274806131224,45.2883377200819 +17.5959233923372,8.43912604156454,48.9144155792504 +17.4989761689229,8.56037765549678,47.1452710176935 +17.5633040171115,8.48312565512305,48.4356438391777 +17.4383617062809,8.67649782642042,45.6160938181669 +17.8027541842614,7.47632101002679,-45.1391041437452 +17.7479217826747,7.49566065398889,-45.1643856859572 +17.9267993884679,7.36983599347368,-45.51453952611 +17.8584880092095,7.40633689343545,-45.0097150293614 +17.9803260519556,7.33773438174695,-45.943072763156 +18.4114124606938,8.46679505893954,45.6251615950296 +17.9979295196893,7.3385047618905,-46.293970663997 +17.9819119284198,7.3402334328298,-48.3078231883787 +18.0158478013734,7.32249970909665,-47.4372727341648 +17.862113712712,7.38863490462911,-48.0758102200259 +17.9152171755319,7.39256570033246,-48.8005354083152 +17.7863680772453,7.46399371712258,-49.109179953859 +18.3629784583159,8.56206888789932,45.8782070078469 +18.2997719218345,8.60183071334611,46.204272047403 +18.2082784654376,8.54816191230305,47.0189120541472 +18.4110201903925,8.54174708554424,45.7637166621782 +18.2003567384969,8.38630208258383,46.3954806904588 +18.1370583120207,8.2894298284694,45.8548097143103 +18.1860854499353,8.47456836587271,47.0632872406811 +17.9425762589389,8.21366367663842,45.2557978079255 +17.9027461717193,8.37591014584751,45.2435981631552 +17.8452583600936,8.40145116378728,46.0280689906422 +17.7817976502356,8.50933630300433,46.4945561470355 +18.2548620987665,8.56873871515918,46.4040943036135 +18.0861043630424,8.20225388731278,45.1999398980622 +16.4831989389361,7.97008963888711,-49.3647920641943 +16.4245525430239,7.93991992481655,-48.5461246867255 +16.2720010065222,7.8377744967616,-48.0008390090251 +16.3319210482795,7.91000029859105,-48.0155235684125 +16.1894370967809,7.77334744568699,-48.3204447772856 +16.2170841398198,7.81036628426655,-48.237689309206 +16.0823186824781,7.65835215112703,-49.8408915244149 +16.05714521585,7.69245946689497,-49.5446050912363 +16.1464097452493,7.71368356221332,-48.7911942450588 +17.7773938476989,8.55094495015001,47.3026817039814 +17.8339451787023,8.45974635878631,46.2268220234308 +17.8262148047488,8.29848933125733,45.3173065078443 +17.7925223357368,8.51916517420591,47.1189243052652 +16.0649582515899,7.74383171784006,-49.2581405708699 +16.0720007808513,7.85259328603763,-48.495264537291 +16.0771349737752,7.94768502014451,-47.0997843763635 +16.0903941540654,8.02764687345243,-47.4826835213078 +16.141838621171,8.13124712498862,-45.8884101477735 +16.1936757679096,8.16295505672717,-45.0727131879916 +18.2639126462918,8.24145440389648,45.7763050908152 +18.1079063072144,8.19961278240879,47.3485458383526 +18.1969293263102,8.20991212048597,46.2804116356774 +17.8916192451505,8.04210108869187,48.003831834145 +18.0014788467777,8.15321755577655,47.6153933427571 +17.6579245566418,7.99910886018843,48.4188314484796 +17.7814722110383,7.96969303288912,48.0460414598058 +17.5429928975363,8.05704566000444,48.436599020767 +17.423331768187,8.10294067961584,47.5442550468205 +17.1598358267649,8.18775688813307,47.1016375285412 +17.3065426514776,8.1633379062539,47.8420919709885 +16.9995747055065,8.35166318727369,47.3479988041003 +16.8222847324622,8.41133008894243,46.8125358477369 +16.6116919464646,8.36283238345904,47.1725032557705 +16.2061288067344,8.22495583560626,46.6789128554474 +16.0014833235518,8.07273842543311,45.4212391295892 +15.8234315306673,8.05094283712573,45.1017393755193 +16.3952345462237,8.26591498064744,47.0598767991682 +15.5794692125894,8.27499710959252,45.1604163274789 +15.4684136765971,8.71402355798286,45.4311023201619 +15.4425609248034,8.77176190997944,45.3972644797067 +15.260252978969,8.7074904367402,45.1549485357537 +15.2253372858882,8.62752614722683,45.0437183759107 +15.1845732744459,8.58793709144229,45.1716178581269 +15.1453417018525,8.58249604780586,45.6110204667369 +15.1229391384001,8.65389734214145,47.608835601021 +15.1716211878272,8.7448961177581,48.6543678019173 +15.229245997447,8.85473092032513,49.4935089701391 +15.4941322223792,8.51097008357304,45.5053674819961 +15.7970655673199,8.23895411152459,45.8873897710467 +15.9224090507855,8.54865393401685,45.1424336859871 +15.7785622323468,8.23885342770845,49.6022851652047 +15.9926140304235,8.64187418394613,46.7178968145703 +16.0542583980022,8.72254957845458,49.6727545363868 +15.6804435515501,7.93486502799046,-47.0506956347193 +15.6834235824349,7.89567506902949,-46.5501144171222 +16.680798298423,8.81257683722108,49.252575630599 +16.6627384836648,8.76928040187053,49.046518795025 +16.6262903935432,8.78315768370208,49.6634374929855 +16.69985004619,8.77046149296762,48.8243193485222 +15.6755499296179,7.8568699561467,-46.5174050157952 +15.6865894219851,7.91859503236726,-46.3672700261497 +15.6762628641992,7.96489854902759,-47.1277293185893 +15.6469783008755,8.04904322006068,-45.220198559089 +16.4256664813666,8.98372622460356,49.0887612488253 +16.2743503469601,8.97384942640653,49.9623573918757 +16.6738487564932,8.93398579945175,49.4808954992156 +16.5936398252917,8.89058570916763,49.7333177197689 +16.805196648611,8.93700297141756,48.7997708830383 +16.5602889458369,7.40147439482987,-48.9160219083154 +16.5770419375338,7.49553963782546,-47.6865373527997 +16.5287787770717,7.74472376187179,-48.0846604604346 +16.5702236743645,7.63500380146286,-47.62810187376 +15.0748427069443,7.93840088997347,-48.8003094880376 +15.0590085849505,7.80063761537965,-47.9052033625705 +15.0075182160247,7.74664978014012,-48.4585661177094 +15.1460324291698,7.95668250896856,-48.164869726917 +16.502730213408,8.02556851458385,-49.3904365674437 +15.8736959921556,8.14912839680537,-48.3698357248824 +16.3335347389879,8.15906971281604,-46.4069316166643 +20.3425228518939,7.74908492719603,46.4866147965797 +20.253874289381,7.66363627289911,47.8215534307083 +20.1437417915386,7.69759523851121,49.0051777165367 +20.0667828554292,7.68102388981329,48.9558996088398 +19.977304151577,7.73451005048398,48.1878026475571 +19.8724614772495,7.76340900454285,47.8135860753089 +19.7952334619385,7.76017817295738,45.5184807685063 +19.742084888139,7.8508661535139,47.76034647431 +19.6175762267333,7.86031352270783,48.2314122400898 +16.0656645780613,8.1394603551476,-49.8070792565368 +16.0269002849707,8.20413299382641,-49.2056053515306 +16.0096857299527,8.40105912957383,-49.2053288777381 +16.1482485646645,8.08274993456267,-49.8236933755063 +15.9869287734304,8.459821064064,-48.8798806744875 +16.0327847239882,8.27246381061001,-48.9682351540261 +19.4477257571351,7.89512426733118,47.7085499297701 +19.2431375668565,8.04257985662035,48.6001200760045 +19.2535198536489,8.25246873003491,46.5751305939754 +19.3598280560892,8.37087128141492,46.6794594493699 +19.477688440989,8.40389147990699,46.1916927297798 +19.3288715375179,7.94289125178435,49.1574559382512 +19.5268227080549,8.43027207713338,45.7500744730979 +19.2154076772099,8.19028832357444,48.0567747428821 +19.5646227844206,8.37511236122666,45.6761494051917 +16.2079316688642,9.04026929122764,49.9606781812107 +16.2662381340943,9.0641004915141,49.9801669237156 +16.1584869476814,8.84230230292325,48.6933872282522 +16.1066352384352,8.76030244405859,48.2789109198076 +16.0771211364318,8.51392314077665,47.9708245960451 +18.8810821674622,8.33049527153574,49.9133873312242 +18.8832863434515,8.19343711989806,49.6835459070015 +16.0813195848859,8.3081301206583,48.0746954993413 +16.1273702733631,7.96798096135968,46.6484540059639 +16.1017693674759,8.11734215037957,47.6973248285319 +16.1770257723377,8.00674838525582,47.2176061749841 +16.3853755009286,7.99838752727651,46.9293134638126 +16.4447171006832,8.0357730910063,47.4880262621344 +16.5227363801334,8.00979851190306,47.6331596281904 +16.3271450744164,8.02105961905954,47.3532499206676 +16.6560728559403,8.00053785456099,48.3898505390469 +16.5947383586835,7.97283977767709,48.0639502296082 +16.2509563393037,8.02198772311721,47.3418118448776 +16.8368877662462,8.55703732635479,48.8256572301414 +16.872186627145,8.74792489545508,48.5833794112118 +16.7301340415883,8.08674227218989,48.9412135772882 +19.9181457260429,8.08520566306399,45.8415707090905 +19.9174641889096,8.1777775142299,46.2555984198642 +19.9057705178909,8.23574714382618,47.838542089206 +19.9360093500836,8.35899518054604,48.8758000555482 +19.940239441612,8.45590807980786,49.7568602478792 +16.7806687884697,8.27367627842042,48.8368886783427 +16.566147277282,8.22361239422475,-49.2007751977881 +16.9167933659381,8.91480955121546,47.4174763285259 +16.9136073914526,9.03148512161224,47.8171953204251 +16.3229983723729,8.27267954774363,48.4992183534598 +16.2484558605087,8.2861783777018,46.3935362595407 +16.9175276325967,8.99495273021989,48.9772125791944 +16.9266328063494,9.05212726196756,47.640996421733 +19.9686273990451,8.7128390049789,47.0774157715296 +19.9538046604123,8.68629404554592,46.7954844836202 +19.9842764100574,8.61449128668371,48.5665011583778 +19.9993801029275,8.63681250354965,45.3723709863442 +16.0892796737787,8.49714301842202,-49.5905743621223 +16.0785832099414,8.21442190338103,-48.7770477752998 +16.0716495269998,8.29831247689345,-48.7691523761081 +16.0292950893249,8.12703078465161,-46.7470507865189 +16.039852509334,8.16956227124001,-47.0268266518549 +16.389721131521,7.72943274079986,-45.0601662451005 +16.4646209070498,7.56221510388234,-45.9707999612804 +16.6279083285734,7.57415141117629,-48.7313218808955 +19.3969202741191,8.05819731487172,46.5812769030949 +19.4661813736682,7.905354774997,45.3242413927823 +19.347589819061,8.2578081457765,47.454717764417 +19.0601841384313,7.86191248615621,45.6243088473578 +19.1194991129106,8.00070184158793,48.15065984849 +15.4525115648524,7.31438615482851,-49.7672266482008 +15.4884799258321,7.24258163466743,-47.1288180589418 +15.5419922397213,7.29874500478202,-45.7220615593745 +18.5478439816718,8.31376870049765,45.8540028334886 +18.4112760052322,8.30288387982641,47.5697634890136 +18.3295556590659,8.36062701083863,49.6147187755768 +18.2627109034358,8.27728307770954,49.6732376325293 +18.0658898827803,8.21331604431792,46.9346004063455 +18.1746870564861,8.24913405202894,49.9569935150691 +17.971427427084,8.23829394057504,47.6198443251426 +17.9092847328971,8.23594884247415,46.1151455528592 +17.8454109880691,8.26703962938343,49.0522097479876 +17.7530076931549,8.24817752161144,48.278210252112 +17.7123469601324,8.28083275374343,48.1051820075185 +17.6631602418772,8.29134213457888,46.9917984282609 +16.8797401519065,9.198235062542,48.4973901182427 +17.1935833501822,8.01838209283811,-49.9343394513917 +17.156899380679,7.96639204872268,-47.987210401029 +17.1051868920409,7.99953694915163,-46.2581248726091 +17.6506340532033,8.31998712000826,46.790716447343 +17.6939017775066,8.34080452649834,46.6730734292666 +17.7494895268922,8.30334202487851,45.9381599606248 +17.8053007768342,8.30366799595904,45.0019135286703 +15.7722524470915,7.65347980038984,-46.1045067846857 +17.9540387554201,8.3339017848885,45.1332995804049 +15.9033527672075,7.61039409662361,-48.339248214761 +15.8605684784263,7.63085413284928,-46.8115108517316 +16.9416780764063,9.12220519967307,45.990658421509 +17.176722560924,9.0445932316442,45.0744979196526 +17.2826777450408,8.95769605991696,45.443194922578 +17.3636512887063,8.89384830027189,46.2773815732632 +17.4532816819448,8.82715181464257,47.3001169961419 +17.5550168939191,8.91146904348198,49.6330205609786 +19.4445453874892,8.27351914978644,45.2971111948746 +19.5279237700264,8.36751879628844,46.285721653896 +19.5300919888961,8.34531763337842,47.5944830804915 +19.4629163299022,8.26432912960487,49.3541478591898 +17.4532375570575,9.02756047599212,49.1446959505286 +17.4024948195351,9.06737077984737,48.437875308505 +17.3885711986333,9.15850262556279,48.7397978711764 +17.3673243534916,9.16919268327783,48.0012175849084 +17.3424649801714,9.23054964271913,48.7181441355463 +17.3208302748841,9.17203594311716,48.544568052211 +17.2663696516227,9.12355527478481,49.0049889720788 +17.2533409131672,9.0498122893019,48.6151114336978 +17.2629935825062,9.03546307065151,47.6438232300589 +17.3357284092047,9.00464924090881,46.2837483226592 +17.3750801269599,9.05130062210208,45.1271621653998 +17.3483652939299,9.25288726152701,46.3708185588522 +17.3250097512623,9.34039612870377,48.2674418092673 +19.473798371525,8.36775676280863,45.9286223698921 +17.2871329872737,9.37277144690786,49.4356857281031 +14.5152463149271,7.70833857089158,-49.9262397789147 +14.6585652649853,7.53304750874344,-49.2716903912843 +16.2540236750308,7.86904576931289,-46.7718100771252 +16.3303886951216,7.87100341302853,-48.0526425119606 +16.3924014109682,7.96532280588651,-47.9341888679128 +16.4920877486875,8.05616564630753,-47.6503190716626 +16.6052938481658,8.11486989644045,-47.7886808086129 +16.224507557276,7.85830798838767,-45.1356445960868 +16.7425218841766,8.19825143324441,-46.439663132237 +16.6694220172168,8.16149206437554,-46.8557471126209 +16.9470872381604,8.23848872840488,-46.7431759232547 +16.8358683728841,8.25321678379346,-47.1416994597584 +17.2191940104187,8.27414311220275,-46.6919865618154 +17.0829979903322,8.22847355704276,-46.8929009573048 +17.4604093546702,8.20149921521129,-46.6958524981911 +17.349818645312,8.22195977384716,-47.1489962530063 +19.7530416705809,8.13394123403205,48.0327565572485 +19.7159550531173,8.13674252611635,49.7592103765823 +19.8100522841521,8.27577635540142,47.1971304256551 +19.7666548517116,8.24605013178871,48.0160855457092 +19.8582513911062,8.41366339389889,45.8946361728969 +17.5696876044425,7.92590050391477,-47.4135834770904 +17.5958407356311,7.71073783633976,-47.9999626442886 +17.5190988035163,8.06823447784211,-47.024434897304 +17.5720304009215,7.62230385262976,-49.8388225221487 +17.5873938068904,7.63340400210649,-48.5217918746414 +17.0851732426172,8.62398667888822,-49.7295104770685 +16.9922443397082,8.68247984881593,-48.5894292095886 +16.7662382133791,8.44923387341379,-46.5679871730602 +16.8808835686454,8.5426623373651,-47.6484034222519 +14.8295882630976,7.45782230136268,-49.7914976221501 +14.8289440861658,6.78601752646684,-49.1273479812387 +14.7444651253643,6.86040067367077,-48.7720813787336 +14.6514381487753,7.02559688146512,-47.7125129198494 +14.5714147028537,7.34815214053925,-48.0149396090813 +14.5927219616421,7.1989774185713,-47.696359828522 +14.5307494742169,7.37995358446498,-49.3850898610479 +16.6621776158655,8.34386370459164,-46.4504342489841 +16.4123882381022,8.15009203103992,-45.3305521196058 +16.2584090025998,8.13357847022382,-45.1606253351093 +15.9553648558604,8.0287063476566,-45.1690961809371 +15.9420907720023,8.03914407538061,-45.6414545619542 +15.9509866549137,8.04202546992139,-46.8175402663578 +16.0029743680176,8.11309112007218,-47.4656523676499 +16.039529607462,8.18237195201837,-49.9779787201982 +16.543944733133,8.20645599285293,-45.8206507570313 +13.6824207327332,7.22558335015151,-47.9809133287434 +13.7610569941551,7.28597622960442,-48.237084281538 +13.8169327529148,7.40845288382725,-48.6967839356338 +13.8567435020645,7.51655002451873,-48.1057529481586 +13.8824930569725,7.4975729611008,-48.6168678986508 +13.9101254817064,7.53581838315872,-49.3296460274612 +13.9244898418546,7.4513292566485,-49.4117390450095 +14.0283372391667,7.41329801527575,-49.5044346110511 +13.6379126990056,7.22071136953221,-49.5686328131064 +14.312438408331,7.49138398672795,-49.2807173238851 +14.1242982398174,7.47414408342508,-49.3558545835256 +14.2307582429153,7.41330751818035,-49.1047981511909 +14.4997486895997,7.51861422312716,-49.7999975505965 +14.4113380567899,7.5048447236673,-49.9088855886915 +14.5092434548259,7.275115573601,-49.2991149759862 +14.4362975630348,7.29117356780819,-47.4305459912983 +14.4064112225225,7.35173900371104,-46.492199736757 +14.4124254411392,7.29463815136703,-46.6696340732009 +14.4648286341662,7.27123334941092,-48.1086193194103 +16.9055986029944,5.16909820107218,45.4053095990895 +16.8160452692888,5.50671945456937,48.3131381626235 +16.6984409278841,6.04061390359495,47.5111369380127 +16.572892603515,6.12980784134575,45.5648597333011 +16.2293382308676,7.16493609638307,45.0066978471411 +16.1289950086046,7.39491781690806,46.4591141913957 +16.0820551268454,7.36659594562887,48.6710530465545 +16.8911940975399,5.32065066304064,46.6746773841458 +14.4303972819234,7.39044654688747,-46.7319276696501 +16.7480671720434,5.68796998365359,47.6767051175194 +14.4215755096518,7.45072138045247,-47.7183562553492 +14.3591737656867,7.46469276768734,-47.6479986752728 +14.4223189498349,7.51613828484847,-47.7565300190604 +14.3215071007088,7.43539353159243,-47.2836959446581 +14.4155834133646,7.46717331408206,-48.0122193788347 +14.2867575194397,7.41367054591352,-48.2033758449226 +14.2294840499903,7.28548269177788,-49.1764651705842 +15.6308845802348,7.38195212902659,48.6369056027742 +15.4919490680658,7.37328181670437,48.7129976045033 +15.5363964441307,7.3815063364715,47.9423399845476 +15.4308638762093,7.44673538765882,49.324550655197 +13.9644645877098,6.92919889509232,-46.4466621137359 +14.1908385121951,6.77511292728198,-45.1602460821412 +14.2218022425909,6.77528456947266,-46.2769170846414 +14.2860278052689,6.81490676912488,-48.2334986624174 +14.3378480382516,6.9004013405499,-49.3761971603055 +13.9797226813812,6.9596437413681,-49.2593280437287 +14.3835520117005,7.07664486961797,-48.792254693847 +14.4257430106215,7.04364038880021,-49.4981143338173 +14.343999215941,7.13477713663952,-48.2323657664401 +14.2331488411006,7.11659982040326,-49.076884932391 +14.2049235017833,7.09182132527626,-48.449053688798 +14.2106453063332,7.14248851126158,-48.7747451299907 +14.1671852753132,7.18005515968829,-48.2351663076531 +14.1319359793518,7.24663540296533,-49.2082299953083 +14.3079921358406,7.15710059533442,-49.3258019729515 +16.5459331135468,7.9693416262442,-45.0223123988613 +16.6423409660982,8.01503443708282,-47.5827305823167 +16.7559777967081,7.99650168629226,-48.653885850884 +16.8860764310407,8.07235978286426,-49.7453728750679 +16.9727050821638,8.01517251649586,-49.7740893353882 +13.762416423623,7.16813662078291,-49.8317917156482 +13.7180285862519,7.14774452341498,-46.9946346185832 +13.7409245380296,7.33323555218461,-46.5859109339818 +13.7554315387248,7.13746852701516,-47.7893850759459 +13.6950488889829,7.24166947874699,-46.9095322233069 +13.6774296515441,7.19343566412882,-47.4142540656739 +13.6685996657709,7.09425314036035,-47.7476166669386 +13.7054655622393,7.24326743816254,-46.4236665516457 +13.7280995658975,7.29130616529143,-46.506961613887 +13.6657005274943,7.05224285142226,-47.7589776289454 +13.6441062088011,7.0138720318386,-47.552377347013 +13.6051862841848,7.1309449991849,-47.8172555867156 +13.5912883231079,7.17698175622552,-47.2453915178373 +13.5935267674103,7.29496409765609,-46.6377261465632 +13.600256826507,7.49055380096144,-45.7330986495743 +13.6262464649984,7.56007061698703,-45.4436578028453 +13.6551058447227,7.56265075972896,-45.3088645022959 +13.6988379341746,7.49477035770534,-45.2045225444645 +13.6965772528864,7.4308692713562,-45.6654902092315 +13.6827657625617,7.27247297576978,-45.487814581968 +13.6390165321859,7.22520753471956,-45.7951420132713 +13.5412728875595,7.15853917786174,-46.5783607912059 +13.5766936711963,7.19298457361705,-46.4669146175716 +13.5745882037854,7.48161284418859,-46.8028853616499 +13.5024489134972,7.17092329556179,-47.199532559274 +13.4149802461134,7.23115406199579,-47.6563779979687 +13.4230819807655,7.18703744098021,-47.1567313389868 +13.4427518711272,7.17861784034068,-46.9435845973501 +13.4216919571526,7.17660790382332,-46.4629719562766 +13.40866454087,7.19769944348622,-46.6768150946682 +13.4427877119947,7.11071461522656,-45.169990222682 +13.307613301645,7.32853284022861,-45.3580137510409 +13.2885877305476,7.36443914655576,-47.662896902318 +13.3321977318001,7.28349424218773,-47.9866637346877 +13.4430372647864,7.16106109643428,-46.2034481517806 +18.1323120850459,7.38912575095998,-48.6728111456842 +18.1996001055341,7.4075456072702,-46.3217235085711 +18.260923078804,7.5668564447049,-45.9872291764078 +18.2951946715635,7.82809998658374,-45.2607287241282 +18.3161261707447,7.99917194264878,-46.1780222005235 +18.2882599562238,8.17561956432378,-46.8665323979858 +18.1872698063245,8.32137622209354,-48.3052785147668 +18.1091805752959,8.40788211517305,-48.6107473777078 +18.0412025228265,8.50873993802856,-48.8338279178892 +17.9711582226913,8.47329505163486,-49.8203424645389 +13.2817985270669,7.40862015206171,-46.7269366115221 +15.6604709737091,7.69532887534046,-45.3257062019168 +15.6523815967316,7.71902059236066,-45.6454056499394 +15.6851078018002,7.7903047520149,-46.8786609880238 +15.6828813099932,7.75039282577766,-47.4244182008568 +15.6574465659248,7.73846547858214,-47.6088316426768 +15.6602693843392,7.61877327103378,-48.1915213824064 +13.3772810101616,7.20606608022411,-48.5890424134523 +13.4065192426163,7.13380515672027,-48.9666961265412 +13.4020098677854,7.10207255935279,-48.4940473467693 +13.3727160940769,7.034679308742,-47.7089702014554 +13.3605729779919,7.00478083677868,-46.9338799057382 +13.3633132138223,6.92692951725655,-46.5368798071804 +13.351709718882,6.83181673773051,-45.5748325076903 +15.6790816994035,7.56502715146827,-48.7911798622883 +15.7684303877596,7.43010283014469,-48.4420291570264 +15.7370254200745,7.42773780698135,-48.1838676050074 +15.8105655039006,7.49096068905701,-49.115560643481 +15.8751462233261,7.6214477952077,-49.0841670762216 +15.8624156012279,7.52613694127145,-48.4474262148801 +15.8967616075855,7.64514674693363,-46.4745049766091 +15.8721632324109,7.63534595084707,-47.0653685321755 +15.930326957992,7.54672587935512,-45.4243235632513 +15.905464633362,7.62632263302434,-46.5451402731003 +13.6641346672012,6.78838199388611,-46.7315840163235 +13.6934840756041,6.78106583280436,-45.4542145703581 +13.7330234309272,6.80485570866012,-45.0141709827769 +13.5962840534196,7.28344236973851,-46.8561147196567 +15.611140859732,7.36760437191858,-45.9492680184614 +15.4939415138062,7.25506750946381,-46.8843347287905 +15.3899117891519,7.17001266371374,-46.718326143124 +15.2965644341945,7.11228478315205,-46.4254342828952 +15.2016932375048,7.14675898912393,-46.8281898519464 +15.1144040411089,7.23508020509379,-46.454575888718 +14.9859825892554,7.28979031182201,-45.8914574871351 +14.7497040266392,7.45409307026195,-45.9634481786266 +14.6090783497964,7.45019785039736,-47.4992935771916 +14.467295302488,7.45053402639058,-47.9504985584511 +14.3483019671399,7.47331525331039,-48.4441288774149 +14.2505210910299,7.50539124971065,-48.4174579719165 +13.1556442648645,6.69980347944503,-49.0302724165118 +13.1311991151684,6.86131708338273,-47.6612873157661 +13.1035235768529,7.00356915573426,-46.5749550876305 +13.0845701662178,7.19779080633143,-46.3753466162285 +13.0274171744932,7.33726888684831,-46.9369464197255 +12.997608913662,7.45894760770347,-48.5861384593345 +12.8894271035322,7.45309674384349,-48.8071657782909 +13.6783800507494,6.81862148709482,-45.5157524538458 +13.5066784375949,7.31172269462232,-49.313751455294 +12.7831251551238,7.32993900909728,-49.0102029795637 +12.7824387441197,7.29682326819017,-47.7117303432114 +12.7971170091335,7.37452301251697,-45.519780454963 +12.7877815243705,7.29168570713576,-45.9277773361165 +12.7326064061938,7.47915284081707,-46.1711493354796 +12.6697788688196,7.63485736051317,-47.6734907805628 +12.5940685626093,7.71935890169785,-48.6398866099707 +13.8614566323547,7.68983149753668,-49.4882100995959 +13.8654528938165,7.68087014564921,-48.9995089319197 +13.8936823316473,7.70880396879147,-49.0642542688758 +13.9126998579306,7.69660939655977,-49.7003189636712 +17.0977981262189,7.61939966028785,-49.7936963718291 +16.9186981375874,7.83393694402024,-47.8710477639031 +17.0019177688075,7.68993137349646,-48.4012940017946 +16.8646743945392,8.02828838908727,-46.5257609650652 +16.8731926338057,7.91171295116231,-46.58338701746 +16.9257677288389,8.08457766960775,-48.375195563536 +16.8905080159577,8.12054007977912,-47.4363388328396 +16.873600324119,8.00825259239312,-49.1245251079279 +16.874703595895,7.75569294062085,-49.4356186774005 +16.9164276307379,7.64904393471381,-48.163718108094 +16.8784381123082,7.70242562144507,-49.4387329952686 +16.9676412041335,7.74100808681299,-46.6861701737746 +16.9253072003542,7.64451207898217,-47.5881862316098 +17.0904175651058,7.92276041556379,-45.6838553987465 +17.0516621755396,7.87367500838817,-46.157240514919 +17.1202150744289,8.11577895576003,-45.4335309042783 +17.1187245588545,8.05783191719356,-45.1603953375928 +17.1558898318208,8.31583876055524,-45.6693699997731 +17.1318605506212,8.20217037615532,-45.5422756835127 +17.2155065197497,8.62981138203739,-46.5766830218541 +17.1988404241866,8.40221995390179,-46.1031965760056 +12.2325957650051,8.10958050206235,-49.263686102565 +12.1935486067819,8.01618283313575,-49.1173824553881 +17.2340664790437,9.15168551625476,-48.1375563442243 +17.2407771224467,9.01432323989444,-47.5308922584274 +17.2392661902809,8.84519745847805,-46.8401684760883 +17.2983521476062,9.06901980538144,-48.7422516230677 +17.2716166689337,9.14870653070988,-48.0302229706685 +17.3674311038711,8.66940854110823,-48.2618043743304 +17.3237869993654,8.84938350923582,-48.6493678704346 +17.3849479203073,8.32142823832326,-46.9926913362172 +17.3610718928948,8.48080083560694,-48.3091457475674 +17.4492692511345,8.43992899827552,-46.4981563543735 +17.4462554146306,8.35840312402565,-46.1021266777966 +17.4997323671548,8.73271575719053,-48.8802301326876 +17.472862413344,8.59055921923092,-46.9591657845897 +17.5270186932851,8.95322308121857,-49.3511716617435 +17.4053320624048,8.3276041920424,-48.4103183717326 +17.3769815357105,8.15193486024167,-47.3169294199641 +17.3200155972677,8.2053631168696,-46.9639945873907 +17.2430521788623,8.28636669678642,-47.0147953073364 +17.1312537514,8.48564758465168,-48.1897136312767 +17.0488593742555,8.60363887988422,-48.8989148215583 +16.9306540731658,8.66518897138803,-49.4027756497099 +16.3319965023717,8.5551162174234,-49.8367149856591 +16.2573830349162,8.44037415851889,-49.9523587130835 +15.9489538798471,8.31546069338245,-49.5543880382983 +15.9938354406008,8.17267140877621,-49.6906094095787 +15.8391077640779,8.39196630722624,-49.5120240022793 +13.7249981278186,7.54014553081595,45.3103651453161 +13.8003668933934,8.00495861748758,45.0677930445624 +13.7433018223023,7.43770544658978,45.025343452803 +15.5403060725352,8.43871734232648,-49.9472826037888 +15.6956273797943,9.20595168885075,-49.9512884932969 +15.642446125456,9.02537826486912,-49.1960998719828 +15.7344841640927,8.84126880846996,-49.54062483907 +15.7703941896841,8.92163880808155,-48.6978294669146 +15.8132232313244,8.98792012640262,-48.8193939339454 +15.7978517997408,9.03686490806899,-47.7519817965324 +15.7888523429325,9.04262761698786,-46.35753757433 +15.7383456840538,9.09950816278809,-46.0852594595863 +15.6626032700248,9.14889007658915,-45.3782633458533 +15.5635400329378,9.280809074948,-46.1099885912152 +15.6850638931396,8.62172315922823,-46.3386542803691 +15.6497095016383,9.26379281388208,-45.4207160602904 +15.5833847361962,9.33906860413671,-45.7093314906126 +16.135114391348,8.59207443916266,-45.3706868182651 +16.1066229899289,8.64525950688996,-46.8513866119526 +16.061671149284,8.64379767834831,-47.4797180715708 +16.0125824426554,8.70979380595116,-48.1498720625596 +15.9942654077049,8.71829912336346,-48.6619957023079 +15.7531311845125,9.24420302358174,-45.2441424967027 +16.0122660635597,8.74412700618618,-48.6149510331952 +16.052361619143,8.74143466887986,-48.9763539247172 +13.4131745345466,7.03853455274328,45.0479597269602 +13.4506191907386,6.92217306585451,46.2332391940872 +13.4842016663208,6.81760369961657,47.4557533009304 +13.5609804033926,6.59311286315777,48.3788539059038 +13.672374365986,6.44846139245875,48.9964286301829 +13.7928404597358,6.24576774696491,49.0234761141929 +13.8592274100698,6.23711067803242,49.8726276496045 +13.9484777675464,6.31261314886773,48.2919512914546 +14.019363810259,6.36406820077875,46.8639474553265 +16.1255251257402,8.69628422857021,-49.9705758483219 +14.0513463666586,6.39597007648705,45.0855850737365 +14.0693693917768,6.60591620652658,45.0197003635582 +13.9456857073846,7.18338964565722,45.2840804059272 +13.9375000990729,7.37678240124976,46.415218417056 +13.895199896461,7.54436405931633,46.4779466032813 +13.8750872773231,7.56926394365153,45.465827394531 +13.9326038181765,7.46560114029047,46.0474750735777 +13.7986392750246,6.96777298555085,45.253239892293 +13.7521674331906,6.83560280732041,45.4119306404131 +13.725393936516,6.94272593222388,45.8081884845569 +13.6886104374542,6.94601238262333,45.7871131240429 +13.6846523240205,7.03798217933146,46.1572992001139 +13.6634273425099,7.167076335453,47.4054629456489 +13.6591288122688,7.1829623802991,47.7420937600335 +15.8835954501569,9.20964883392958,-49.4302935574884 +15.838186510533,9.31993207924723,-48.9472749824676 +15.3023211020238,9.09439194727135,-49.3567955480702 +13.7710400858296,6.903523610629,45.4077990385749 +13.5832288744131,7.10162046942168,48.3291835289498 +13.5786845842154,7.13123173340253,48.0537299064165 +13.5606395725101,6.99006534049577,47.5419590717647 +13.1926735112196,6.5190838281198,45.210109574239 +13.1186621174648,6.5316440884833,49.2345492279518 +13.049770193909,6.52031194363907,49.9174095510953 +13.147429330551,6.55343993945104,47.6815863953771 +13.6225949613486,7.16853261350319,48.0251939482134 +13.526860845527,6.82951773287042,46.4067937461078 +15.0303339931148,8.91676741006614,-46.0378513519722 +14.9506270337445,8.9174619601255,-46.1302397331234 +14.8647762652566,8.99697460143399,-48.2793530859153 +14.8765271796966,9.11010093795544,-49.0077389837259 +15.1630787113832,8.97394024565833,-47.5464983360416 +14.8812052404421,8.95281878634084,-47.6743132376598 +12.9525569944217,6.70664371901736,49.3957348972564 +13.1259444018103,6.68801486158748,49.775340761573 +13.2244687384879,6.66058895184396,48.7024759222418 +13.2825648163219,6.69108296350478,47.7208298266241 +13.3400661766877,6.83755867611469,47.0369487183851 +13.3942164226575,6.89597800760855,46.7913552739798 +13.4314596395481,7.0443408750392,46.0167828299514 +13.4593243212438,7.01408404685516,45.2747172491035 +13.4548556716625,6.50254438844213,45.8604197143038 +13.4556417194533,6.48025302724282,46.1042078031469 +13.3368674328876,6.45773737121586,46.6000133293062 +13.0422413576936,6.66897866963649,49.7213909797416 +13.3257332737221,6.32962179496639,47.072430266639 +13.326938936065,6.4358641690315,46.0953368040933 +16.568811900089,9.33743946632982,-48.6119352461548 +13.3256057123333,6.5140288974107,46.9090709365586 +13.3631011892762,6.53104992675977,45.3415882495808 +16.2434498584086,9.25335979384795,-47.139667643529 +15.8321384365695,9.86314047352901,-47.2813786286208 +15.7466788441509,9.97025265287789,-46.4878417989281 +15.6854159173853,9.95558204453272,-46.0603498613325 +15.6683567847332,10.0136779926521,-46.0243396448227 +15.6813894121424,10.1725089366819,-47.7158201667766 +13.169516514141,7.12002230085227,45.6055003435608 +13.1581615351879,7.13841160046354,46.110831425511 +13.128720908351,7.18662469600234,46.9344266675975 +13.0915704051509,7.16320355804575,47.1030179921904 +13.0887644071795,7.18830285746909,47.2061549746559 +13.1078060957495,7.20954250267248,46.3020845428881 +13.159845016912,7.21372243141256,45.0799514280494 +15.7793837413996,9.67188518079411,-48.1965145258642 +15.8708040291057,9.4245266647687,-47.7091687714962 +15.9049296924938,9.22270301478579,-47.7831469775861 +15.9310739070607,8.87409594657482,-46.1301388565767 +15.8880756328718,8.75290595124093,-45.1967240910568 +15.9135009791503,8.7176271621664,-45.5725411947069 +15.9463361496068,8.69994001247719,-46.9699807719728 +15.9147917086522,8.63370784862518,-48.0168376821307 +15.8147580187195,8.56621103776366,-48.3911542392203 +15.671717614048,8.52568406532781,-49.1836595784539 +13.3263954698174,7.37536649121791,45.2226898426624 +13.2994178387962,7.38178389057206,46.6634375086078 +13.2972593952145,7.26221206843212,46.1888342373627 +13.3240347287898,7.09473018090142,46.4190141875243 +13.3585910320814,7.00745580068564,47.2044247337782 +13.3579651506655,6.92474469456493,47.057836855816 +13.3630030723168,6.86074620180082,46.3830640718722 +13.3805282914164,6.83569824514896,45.298372496606 +15.6999506253477,10.1942886452947,-49.3776566810895 +13.9198849947897,7.1752236810857,45.4617264874311 +13.9642096044092,7.25949764131279,46.2896738298512 +14.0104401230154,7.26795277642344,46.562562622753 +14.069165988227,7.26464535414839,46.8574919932888 +14.1373703025123,7.25965802716427,46.7384059721189 +14.2081218120225,7.20114332819311,46.710935767827 +14.2679337681222,7.0691365737944,45.5781400724819 +15.5527096356971,8.50597596533297,-48.2955258744997 +15.4438620630668,8.47605859690891,-47.4280475402041 +15.4166154479259,8.46549471018214,-45.7068894927858 +15.3938483725034,8.38350380346031,-45.2193057132903 +15.4223809520858,8.19852411868616,-45.3796092737193 +15.377574665838,8.23604336021592,-45.3655928122708 +15.4843768094137,8.2345102778078,-45.7595223590061 +15.5593962515679,8.27765573070186,-46.3657985399913 +15.6552709005308,8.40483803641765,-45.3501148988202 +15.6940754678914,8.50167140702227,-45.7681904333045 +15.6945592354342,8.60128041238046,-45.1481845823999 +15.6753507567796,8.6494018667191,-45.0463116495477 +15.6620710203631,8.6897907766487,-45.4905985591357 +14.5755819154975,6.32615118409827,45.5701263978948 +14.55923290186,6.47738913046932,45.4909451379124 +14.610172109474,6.14553238492898,45.7780915288835 +15.6430375129619,8.77573734354624,-45.6273595246856 +15.6562774614274,8.75305218903122,-46.073959120191 +15.7068284825815,8.72439497406914,-47.7247365686804 +15.7091701444082,8.78574498677863,-47.2953960439564 +15.6677604107391,8.75134212616766,-48.6352005903629 +15.6844749446753,8.78774786350699,-47.8634864969518 +15.5355719240341,8.80700010366007,-49.7879836502126 +15.6006581779556,8.80749360945374,-47.801945566834 +15.4580434499344,8.79557347764866,-47.7453089739527 +15.3212498433176,8.79844572576698,-48.6027755948521 +15.3505133123819,8.78940275787676,-48.7862911788957 +16.0680175011233,7.33668828684233,45.7392416366139 +16.1398702200372,7.44020895599296,47.4358322187868 +16.3012022321744,7.5329296847514,47.3791291343828 +16.4100784267691,7.65165140472771,48.8807617269201 +16.669467395403,7.78830040527791,49.8341094373571 +17.3956969841698,8.5651348271496,49.3442830115632 +17.5817116220487,8.73354827897636,47.3371784479226 +17.4958766555999,8.67129137964225,48.3361113958 +17.6974854375314,8.59762402262477,45.7481872585851 +17.6498693900699,8.7011059348226,46.4249465656722 +17.683463330273,7.85189879871137,46.9413817400047 +17.6045844498112,7.99381185369197,48.317447840782 +17.541487155243,8.04757832115619,48.9486475176768 +17.4851442279941,7.98019899124287,48.2264266359621 +17.441482291704,7.98979827357812,46.6163823043015 +17.4266020859604,8.07089213492536,46.0296846975107 +17.3814527294303,7.99410104372827,46.7057412378315 +17.3200016784078,7.7871608204787,46.1189881694308 +17.2485963809827,7.73806906720017,46.4009233672334 +17.2283911747521,7.74366569000731,47.8103546257154 +17.1822640250817,7.73045106670144,48.5416290976909 +17.1890287896558,7.39669791790494,47.4329814590324 +17.1950868979164,7.56949978317347,47.6890047282854 +17.1909057349343,7.51762586504292,47.6233899589915 +17.2004304652621,7.44137509280554,48.5942944824502 +17.168786417982,7.20193507432934,48.6090729707903 +17.1062151834018,7.0742070203931,47.9811350785337 +17.0441367090924,7.18810885415514,46.106125744543 +16.98521560773,7.25800283871209,47.1673534202218 +16.9486573287057,7.33189864316941,45.3785805280222 +15.5683558253408,7.43406571387531,-48.3100570006458 +15.6538388257996,7.55601952354766,-47.1296208257937 +15.7336562221154,7.63829369803554,-45.6069848254034 +15.6403511090779,8.55266312685269,-45.7604320644023 +15.6698762695501,8.73359863748211,-45.939774550137 +15.7700196782198,8.87193540375577,-46.417408223573 +15.841446292427,8.9588504472429,-47.1421330886536 +15.9927115890903,8.99704475022203,-45.1925030029341 +15.8997348725541,8.95515715823805,-46.3172261282678 +16.0764046438527,9.29977294231221,-45.5914935157644 +16.1423476897208,9.43420378199723,-46.1904683437113 +16.3100407156782,9.56086052123673,-47.2059918912915 +16.2410788776831,9.53436925131606,-46.29462810371 +16.3494913067955,9.57210257917205,-46.7529515917638 +16.4010366669017,9.69575356665203,-46.005934209599 +16.5199935551667,10.7111920892384,-49.2860194647373 +16.895255323367,7.48957332204299,49.583659042677 +16.969216250343,7.60336075269833,48.1766017372199 +17.0825099872087,7.7440181502613,47.5393318721624 +17.1987027285667,7.79203213721709,47.2840263812293 +17.3596766712808,7.86918509964783,46.017999979471 +17.4717100375692,8.052295849063,47.297641828382 +17.5318137211552,8.10642215923022,47.2882027654408 +17.5272573869155,8.10891712156561,48.1758250450089 +17.4164012227467,7.93215063482683,46.4711881263143 +17.390551020648,8.45740342002474,48.2217090318222 +17.3241384192077,8.51362047926148,47.9625674138899 +17.3038032102396,8.5792600860445,48.2380832030692 +17.2781681273245,8.53938288846864,49.9474593054274 +17.9172018167442,8.13085038778434,49.5201601851736 +17.9773779006975,8.07724248767601,49.8633622159659 +18.0592421014998,7.95824546025289,49.2979355256356 +18.1282312812138,7.972365231216,49.2576648281457 +18.2260562840485,8.00174524929283,48.9752663773568 +18.3136110173636,7.99507516767022,49.9735713674544 +18.7884945052073,8.18776373866586,49.689151159681 +18.6529616018851,8.09958566949621,49.7202660679358 +18.9155359727303,8.2529395401534,47.7169880913458 +19.1121102896085,8.38657811955576,48.5482942490053 +19.2905435545944,8.4691837184773,48.9489869589742 +19.4104406518683,8.4846572710808,48.746595729274 +19.5028913822593,8.4382832077338,48.6904115672814 +19.7757174510575,8.24029868490526,49.6632327742113 +19.7800145732818,8.15021654667233,48.1725967592309 +19.759892806001,8.06578184469915,46.5018920741537 +19.5464154029663,8.19855095974432,49.683739968141 +19.4347475348693,8.31056598285822,46.8037154310761 +19.2799481509611,8.35317239575691,46.9945131546928 +18.9563675377326,8.24224171374336,46.537305845712 +18.8871110114337,8.20662273582606,47.7462825400969 +18.7760710908452,8.0735746721705,45.4156634200552 +18.6190816060157,8.0756020480269,47.5487854069645 +18.3361268080851,8.02194035846529,47.4464217985004 +18.1824409991114,8.07306050246521,48.0577970368889 +18.0582823470546,8.14614431443226,48.060687437808 +18.4625522200593,7.9901786121869,47.6018711038815 +17.9507975141743,8.30535452176195,47.9556282082241 +17.8752926865682,8.48062812221878,48.6115509697408 +17.8757464876099,8.68802351258955,47.8035064918192 +17.9084932861929,8.82811732942104,47.6405101528008 +17.9785672127761,8.93340252918031,46.634779844272 +18.0343479972047,9.01029424126541,47.1169537254964 +18.1587064362402,8.280030617049,45.2216537611961 +18.2377939839402,8.31748942406016,46.5004021692778 +18.3214336725465,8.25922806605207,47.5887093976382 +18.4242017221367,8.37405470238301,48.3045179414428 +18.5178801976624,8.34839979186673,47.7582143213791 +18.62536073421,8.55994447224273,49.2508442665362 +18.6651767077089,8.68039099257228,49.1607311301972 +18.7350453514703,8.67663307087175,48.7684500692379 +18.7643724011415,8.65079200463362,48.1386194185733 +18.8199551492893,8.5948517448136,47.2571158512295 +18.8723884721975,8.47099097942429,45.184140188857 +18.9496069466102,8.44494604102313,46.1173048835278 +19.0418088333422,8.47479868053315,46.7808425797958 +19.1565123025098,8.49808882440948,47.4639872146448 +19.2734658291365,8.61065456327486,46.2182105772222 +19.3971591938044,8.7956940186994,49.5771487801314 +18.5850809595457,8.50274189813154,49.2495193909706 +18.7517028720217,9.00521563050649,47.7924664301916 +18.682189173075,9.05617910845527,49.419509952231 +18.065264852585,8.57689995844686,48.453881141914 +17.8962513436759,8.69714788484008,49.7478818186229 +17.8920618051089,8.58709241799807,49.1895725959719 +17.8782231393927,8.37702430973208,49.589682011536 +17.8197528935222,8.64695643809184,49.5353703965622 +17.8364878952739,8.7269248341214,49.087251935216 +17.8462463568099,8.78905682919907,47.620469811389 +17.9089513858973,8.72582959188458,46.0556691940944 +17.9845433825281,8.70780024999802,48.685008137643 +18.054814066489,8.5513644050759,47.2032816075435 +17.8326669088001,8.0971236967931,45.1205573004263 +17.7343282239964,8.21749844646701,45.6291400574343 +17.6398273979435,8.31637072381969,48.3296257369617 +17.5578277014539,8.44629845839345,49.0266498530759 +17.4761594038286,8.58126314120251,49.7750447171481 +17.1916388092128,8.68407845469431,49.6763741911744 +17.388627283362,8.73351318054865,49.6191434151096 +17.4914287816395,8.49042812019404,47.2779942075193 +17.5395260902632,8.36193220694992,47.4197270486123 +17.5730644937175,8.26092332964806,47.3798030152273 +17.5636933333947,8.15190624098662,49.211887135514 +17.4108570618422,8.2595801519886,46.2585307113123 +17.4398037471213,8.24721028789343,45.4390226669538 +17.60237446844,8.68622998524936,45.3016774927895 +17.5927177650552,8.73244332168972,46.6324376481116 +17.5406792952445,8.64960697482727,47.9318295723734 +17.5296948456078,8.6504217456615,49.1538812914683 +17.5273984859968,8.42136794374452,49.8723958013503 +17.798930923658,8.28494199763635,49.4050858041085 +17.8107191739789,8.2947351243244,47.1268083655555 +17.9548364413861,8.38171669821244,45.6236995414703 +17.858546294829,8.35023097720678,45.9850016982899 +18.0242202790923,8.4835146575126,46.0253527717248 +18.9113455198892,9.61522755602547,49.8564430255381 +18.2195718025579,8.4472662190798,-46.8695023292235 +18.3250139893611,8.40630181634576,-45.9076543008258 +18.3833972870153,8.42054245986601,-45.5854858009537 +19.3845783568307,8.87278249190635,49.7588828413151 +19.396917719407,8.83463309337352,49.7523648010875 +19.386973854495,8.86314981984487,49.7219928694055 +19.3867954838836,8.78875190090132,49.5745538975027 +19.3656952865575,8.87221741319193,49.1131545697218 +19.3100947309646,8.80141278079565,49.6682843714126 +19.7945289794567,8.81526477076515,49.9533019830348 +19.6646457408989,8.64833886352959,47.1046751047783 +18.4171112751274,8.29766023121995,-46.488712026328 +18.4871912874934,8.38698077605423,-47.9849677697505 +18.5221102709423,8.3436992718552,-47.2594061219183 +18.593163169088,8.29235845187045,-47.170280283992 +19.6907017858008,8.75028323987713,45.5182432321992 +19.6388820878384,8.8448453557389,47.2317833022856 +19.5096824177241,8.97490303510786,49.415121612347 +18.1261994168533,8.79289846136138,-45.1978531916168 +18.1376055616254,8.94335275484867,-46.475825925064 +18.1382725162596,8.99349120217227,-46.586160565838 +18.1483956876421,8.95180219532823,-46.6034911576947 +18.2444920632189,8.74210934943968,-46.6309490946631 +18.3680689387452,8.54842461636822,-46.5839036967699 +18.2908134129442,8.65290949291823,-47.7260833840518 +18.3411784402548,9.13737881616256,-48.5490986261046 +18.8237506869744,8.59415114311912,-48.6436898949631 +18.3609886486034,8.56294890840192,-46.6618382722515 +18.3069607845134,8.66480066115165,-46.4526867206489 +19.1636715802241,8.63878930589793,-45.5092998183522 +19.1082466432498,8.5781276231151,-45.294152858868 +19.1255748407455,8.49038848517457,-48.6999814018456 +19.1923770846998,8.47608520983844,-48.8734319581792 +19.2748289898819,8.36741851900295,-48.2464091655733 +19.3398817660505,8.38338145739278,-48.1196668234159 +19.3685649482375,8.47147682642469,-49.5905996051969 +19.3458103301993,8.6763679305497,-45.8772505779717 +19.3020054607968,8.72246707218945,-46.5361274274198 +19.2979034594378,8.63349331630296,-47.4332044495752 +19.2516897864106,8.48762953427914,-47.9017924743456 +19.2161955008399,8.31234863884947,-47.7624807446368 +19.2166335951446,8.06506839717874,-47.3977155537714 +19.2310061002119,7.92457588088441,-46.2036992032388 +19.2549488070826,7.85409341112285,-47.0472588247466 +19.2866184392222,7.89347781621514,-48.2776264396954 +19.2661104009802,7.9042485602653,-48.4859247324243 +19.2337932288895,8.02663221702834,-48.5333868148106 +19.1938494082468,8.11337430497611,-47.5131080315469 +19.1758650899425,8.22969407864442,-46.6559887225371 +19.1491919871278,8.3882029604995,-45.3733645256975 +19.1610980143582,8.63438580780063,-45.4634247169939 +19.1668822598586,8.88091353540409,-45.6914981547205 +19.1707972811368,9.1142731721717,-46.7010554863261 +19.1292278689291,9.21925889413143,-47.3586490976197 +19.060582702516,9.17678872554291,-48.8218621190378 +19.0226134190547,8.99852335877551,-49.461900720353 +19.0119435976842,8.75329717375283,-49.4532006578605 +19.0020685279486,8.46171105181677,-49.0939367122466 +18.9730413260426,8.27657863238649,-48.8997098656946 +18.8633110667853,8.34022179309703,-45.6793095786193 +18.7607205254098,8.36394562926824,-45.4673641562505 +18.6832406609458,8.41272866196235,-45.3238328550668 +18.5971547453594,8.49653461114704,-45.7954058967829 +18.5128865448762,8.55303680956007,-46.4052484802337 +18.3987940728489,8.66277042556567,-45.8696062699511 +18.3374032690538,8.72001477494026,-47.3782002215237 +18.27585352953,8.82734813189316,-49.6555092689137 +17.3805208796872,8.93211396720568,-49.8163245280538 +22.4916181450631,8.25673782864283,-45.4852400380696 +22.5429664183219,8.29482717442708,-45.6792698862272 +22.5740247222617,8.35945157438305,-45.9865372769905 +22.4820710439156,8.4396824420333,-45.5232911270494 +22.5507769995115,8.43698649379756,-45.9634759525248 +22.2985171213819,8.6252434307361,-46.824226004858 +22.3987910060003,8.52388704628995,-46.318494298342 +22.175694732239,8.70836702476026,-47.0559734760359 +22.4389124592156,8.45086439562535,-46.165258339268 +17.2372335276539,8.93134819361705,-47.7587987444974 +16.9555135119558,8.65422539487906,-47.6193909095214 +16.800252825365,8.36206887450204,-48.2019601845028 +16.7351683830522,8.31505695347142,-47.4355011316599 +16.6797152730461,8.3310402595708,-46.1869647823116 +16.6467520496051,8.4231782632209,-46.0798936240593 +16.6128739928715,8.59057661177693,-46.1154193825024 +16.5824484254307,8.69226926371005,-47.7077833288295 +21.6572339825376,8.32154302760569,45.4391398377314 +16.5721400696355,8.70921999848297,-49.157537942027 +22.2745909433481,8.32998525359287,46.8316305468538 +22.1956086020686,8.27090265973594,48.0754788402838 +21.9990063992749,8.1701142587914,49.8731949043228 +22.1215326546309,8.2164790305871,49.5518447259108 +21.8916573479798,8.05714883136659,49.535566082947 +21.8698311796052,7.90184628862329,48.1419081054399 +21.859948591602,7.72617305110077,45.8846391542822 +21.5442622207072,8.04400753679659,46.3284489577256 +21.4548362806737,8.30915653620577,47.4390632404629 +21.3507281961471,8.52251496097694,48.9339702332534 +21.0547434751911,8.82107917820619,48.620362784123 +21.2237371282571,8.66034670576629,48.9645886251122 +20.7589214155204,9.10245176276632,49.023381295358 +20.6427968851846,9.13489143784717,48.5354232652885 +20.5355882207842,9.11569629664445,47.4539562252889 +20.4338162602402,8.99735401313306,46.125880740619 +20.9053570022066,8.98716165298374,48.6760062579214 +20.0915443659397,8.63578364319799,45.3381138810069 +20.2199034468588,8.72797967487455,45.2401179327293 +19.9478782287698,8.62321176214117,46.4493207250698 +19.7379748891353,8.79411647265169,49.1874598582275 +19.8237238552191,8.74603829366342,48.1908745255354 +19.4746243890681,9.05917143979689,49.1103057594677 +19.337183303776,9.1004876396432,47.3372953701648 +19.4673789547555,9.09837627410212,48.4225885449472 +19.2866475044125,9.12673885955937,47.7673441425444 +19.4519042437241,9.1019314502149,47.909126337316 +19.1857404853116,9.19020175289002,47.9689275593268 +19.1286926928328,9.27873797380055,48.8189722811767 +19.3985772144003,9.12582211967586,48.5246295578896 +19.162276204761,9.31480047843021,49.314504793443 +20.3090238607567,7.98952149253607,49.3230055787922 +20.5652184746229,8.03933931617364,48.1347967908629 +20.8298750657714,8.66887417982609,49.9880162524821 +20.7283522528495,8.36143146842522,48.9225957834146 +17.7857186720727,7.72674208931194,46.9841430956127 +17.9039416677466,7.65847074094599,46.9846543000675 +21.7738447668914,8.85780564999136,49.1563238103045 +21.757255037422,8.89469275788036,48.7196296748966 +21.7581282493834,9.01922665696638,49.2392124552316 +21.8234152852176,8.78617598373115,49.2311621724225 +21.7457760432892,9.03378513599137,49.1349236106817 +21.7967673237442,9.0198235479269,49.0571578807265 +21.9284914266244,8.95486054004442,47.7015090413632 +21.8749992777543,8.69776762018618,49.2135456210387 +22.0414848326439,8.9135528818842,47.7493072610932 +22.0516684838705,8.84672805397953,48.3260609005397 +21.9628223610144,8.78680575589009,47.1119079824859 +22.0451064800206,8.76204495875363,47.1033721871557 +21.9589722002866,8.78304805616668,46.1847322092891 +21.9287197260363,8.79200341104073,45.2263358809737 +21.9917560653226,8.77395263100977,47.8387037511937 +21.9556951021072,8.85741386165793,45.5466535020297 +22.0250567732997,8.83656179569598,45.4343871345527 +22.0340066948457,8.77323855187405,45.3032033538029 +21.6069212365616,8.85206270933819,46.4129465714341 +21.4890202698289,8.58723799989233,48.5162062042956 +21.5473464992345,8.69894214313079,47.6170293013435 +21.4609383405615,8.46068421079177,48.452823281007 +21.4451355610241,8.35447392578002,48.2678193395608 +21.4307508967091,8.38547624666309,48.1896800162624 +21.4055335683639,8.46518397946764,47.9632113882475 +18.3369958814456,8.40885673638442,46.8164329110695 +18.2876602924923,8.38118398056916,45.975797672795 +18.351431518541,8.38309851168924,46.4950663476566 +18.461220384994,7.94377921681102,48.2516338414312 +18.4645373095887,8.04160595088546,46.4901248171553 +18.7697025912414,7.49000983881103,48.0211882685308 +18.8113909846407,7.57669748873672,46.3694605888335 +21.3794966907656,8.65763244858685,48.4709566873598 +21.3800338843598,8.74976097197282,48.1637777165786 +21.3825144806948,8.89880403602898,49.4339938741977 +21.4036742216605,9.033240970546,49.9937320838328 +20.6940211269614,9.33153317921209,49.8378858053199 +19.0730888707545,8.03722593575472,47.5529571565896 +18.9933089081988,7.84027893368876,46.3254922919998 +18.9216127048367,7.73070305490277,47.2450508825261 +19.1744616557714,8.33340333837564,48.0131181937618 +19.1743079336566,8.25235336670921,47.8582084738554 +19.1459926773789,8.23030882035582,47.3424786614564 +19.1455623098928,8.28751172596135,47.1715851122227 +21.0058665952095,8.35750625425366,-47.6042545403728 +21.0471500627479,8.43424121639123,-49.3183089467225 +19.7295952276052,8.0224636700265,-49.9348864082052 +19.7943842620128,8.0639304290264,-48.2691828892325 +19.7943420753621,8.01906048907209,-49.399068867162 +19.8671695143671,8.14178738840552,-48.1942955169942 +19.8493726260618,8.22511794502984,-49.4841588231306 +19.23521025056,7.97334710904786,-49.7288937346428 +18.8564311976121,8.39955690184825,-48.4308639699528 +19.0935119726808,8.05894535622736,-49.1680789765449 +18.9522523067119,8.19724313051201,-49.197767800184 +18.8469229559968,8.59842579943313,-48.9077973702812 +18.7906873076957,7.91881519539773,49.244564206856 +18.0284649854398,7.94398227034289,49.3896613215427 +17.9967973664279,7.87482220344792,48.0096750181158 +17.985381677727,7.88632866912601,48.3654456519256 +18.6922036775,7.89751796840359,49.5076923107442 +18.8782045163681,8.80006545273209,-49.8778320807664 +18.8444441376562,8.71902083356821,-49.5665086718069 +18.7633621004849,8.05249242838406,-49.1103804184833 +18.8183590644018,7.92430934708072,-48.8220514943621 +18.7596534272219,7.83050067244381,-48.3166400805194 +18.0739144929838,7.96124426822435,49.9254801633247 +18.1194135086757,7.91247456679824,49.2528654602764 +18.5110425341947,7.56648712828187,48.6210566772012 +19.1303157340792,8.41730178814164,48.3732722485885 +19.2489104261749,8.52864907938326,49.7587946726325 +19.3661644289122,8.48671045678412,48.7620684248648 +18.1819026406977,7.77081525113553,49.0248398018477 +18.2706693284156,7.67325451423196,47.0467498141365 +18.5365457083409,8.13677077293838,-48.7319904551928 +18.4892030587672,8.19476486570762,-49.57311264316 +18.7032433981682,7.91787829641449,-48.240102699839 +18.3964331498176,8.13986775224651,-48.6671854999712 +18.6051662110624,8.03963340369468,-48.2734130169089 +18.172597798562,7.94971698235061,-49.2513137760481 +18.2258602593272,8.04148921257508,-49.7916582572809 +18.2865912272823,8.08877288539882,-49.2033819205446 +18.1124333883668,7.91898849738509,-49.3120979372234 +18.0347749720854,7.98161555095868,-49.5142403633972 +17.9718840404465,8.07714691312776,-49.6842779751633 +18.6469680399079,7.93058243251751,-48.1015754003623 +19.5567020504726,8.38062141574822,48.2966671731121 +19.7425103546138,8.2457591241663,49.8773161976107 +19.7823933817018,8.16033362696846,45.8784037307287 +19.7812193503131,8.07263030492186,48.428445488452 +19.7961440145555,8.08240189085671,48.6802929438263 +19.7135181946868,8.04547695921105,48.8905466296112 +19.3852520285341,7.87617202991122,48.8956431435333 +17.6873339406802,7.89542000558167,-48.293154505218 +17.7557911709503,7.61710517530013,-49.4209424812214 +17.7417447216243,8.475166154245,-48.0744513571211 +17.7242373933103,8.4378986143218,-48.8380540226795 +17.6842077334085,8.23418802855093,-46.5186494277345 +19.262176719757,7.78054219725786,49.5105379070334 +18.8738562411631,7.79590455923123,46.1984856071135 +18.8043648047913,7.82806920403993,47.9802633614087 +18.7093628919967,7.72027627219977,47.5753965613304 +18.6474910140625,7.76525511743124,48.6366488555116 +18.5654904760872,7.73152460721053,48.7222525052621 +18.4855304423975,7.77844615415886,48.6980965563928 +17.8032688329799,7.93574539379324,-45.244977989756 +17.856605824433,7.88494381136404,-45.3332228852001 +17.8787531151383,7.91482960339017,-45.6500511425601 +17.9365269291122,8.08466727926108,-46.1055780166878 +17.9134317923469,7.95805656535336,-45.8559719342095 +17.995198904587,8.2137648785901,-46.275476923862 +17.9483964051235,8.07240041152446,-48.394392546592 +17.8823607768394,8.07321612606139,-48.6497934160904 +17.8068270176299,8.09613261390276,-48.5076695798686 +17.775580432509,8.03352071255833,-45.1056215330496 +17.7061046491815,8.1503762005878,-47.4282626359584 +17.6872014681204,8.09539159315939,-47.8156881687244 +17.1285069643917,8.12940397530586,49.5675913368769 +17.0783047726996,8.0413584775146,48.8732377071743 +17.0604656127537,7.96631366432196,47.62129133707 +17.0247932109333,7.89948833066083,46.1253909464097 +17.7587317914478,8.13583216115426,-48.0799167597088 +17.8094802469785,8.08780662004946,-47.5022011295586 +17.8429329712064,7.99122661495197,-47.706034330125 +17.8759491094457,7.87481912565264,-46.898129951393 +17.930521083303,7.72963202576813,-46.4535896802576 +18.0615784784227,7.35355659011197,-49.7831074321853 +17.9827190920552,7.5693286164661,-47.7942333191142 +16.9284593425765,7.97846784444729,45.4855072380807 +16.9478661743972,7.99715973924411,45.1739307064338 +16.9268098067333,7.98678690020711,46.0146206429301 +16.9890690162502,7.88054929834979,45.8656416578947 +17.1261200524766,7.81482982726716,45.5737487224405 +17.2734177922347,7.72949251742036,45.6212857519135 +17.4251481999758,7.72680763569001,45.3120989030849 +17.605073151682,7.80446761918255,45.180968837609 +17.641546745478,7.92829783467106,45.2714109571679 +17.6466576155538,8.02196456929414,45.2735767729174 +17.6590855227009,8.06935562401138,45.256135892128 +17.7033707033724,8.14150121013666,46.0767674398605 +17.0074836130633,7.96606736443113,46.5026352906764 +18.472809636322,7.87386476790506,-47.544782063235 +18.4096593764627,7.76614797793602,-47.5470195726965 +18.361545533436,7.8219827921953,-47.130105493095 +18.3095196949432,7.88073667594305,-46.2533155430554 +18.1386122459838,7.9905364084737,-45.1352447297032 +18.0486640759399,7.97584233297392,-49.1141819772864 +18.2364194977598,7.93823199884205,-46.6759038344337 +17.722077415306,8.14323189327656,46.7498190015646 +17.9106108904242,8.0804634372246,47.6860843232434 +17.848380152442,8.11784911127457,47.3338806373526 +18.0236998588856,7.9898276590854,48.0533218343379 +18.0782426532485,7.92057560632266,46.8649850433171 +18.0462914973376,7.88863441772141,46.3603484698004 +17.7401254115533,8.1313146656149,46.6935821858916 +17.9582369296077,8.07776103255283,48.1110096373015 +17.8798892676246,7.82037353715065,45.4201077165935 +17.7898286529928,7.70934623955591,45.9457127112183 +17.7836001418421,7.66491812061494,46.2517154050296 +17.7931701706773,7.85738878920066,47.280505664631 +17.9478058883555,7.85389539757588,45.1625416327718 +17.7922331364668,7.8185309788304,-46.1834355759953 +17.9228402792259,7.88362301023158,-47.1592961286679 +17.6873085694965,7.74351985143677,-45.4424066627595 +17.6093884705534,7.84797830424316,-45.8799652496835 +17.5252599654088,7.82732022709985,-46.5018253233215 +17.4550974020325,7.8593346310378,-46.4972895737056 +17.4364692967609,7.83339231281298,-47.2494616337942 +17.43818783135,7.82977901988804,-46.9330906242572 +17.7726662999957,7.95299394270836,46.8102407062337 +18.2731822077351,7.95168897037523,-47.743405326537 +18.2778357883905,8.00070237852426,-47.9693552609186 +18.2707783783133,7.89587809345951,-47.4927812372293 +18.0578879808089,7.61412456582291,-45.03201319537 +18.230634179818,7.79127294420979,-46.6771341077786 +17.6058934892831,7.48113845900855,-49.0590605436255 +17.5033206809715,7.40557775594165,-48.9511182521917 +17.661829184439,7.53850999208201,-46.8111885077865 +16.7930760787589,7.44821429902409,-48.7186483844102 +16.6393975710434,7.79469908068053,-49.1097561502999 +17.3933886112711,7.34724942810932,-49.6396594920527 +17.1409209238175,7.27067209902595,-48.8958021769532 +17.9156950221966,7.82233704375975,-47.0636700397977 +19.5706697378195,7.33762118289379,-47.30583522612 +19.6590939990306,7.63233897557557,-48.2016009285023 +19.6779961164083,7.77261495838281,-48.283871907602 +19.7047764292897,7.90413525197414,-49.5590206983681 +19.6433617922482,7.48926919845156,-48.4001194265308 +19.6268690317066,8.13198996985562,-48.5827361120943 +19.6633046592829,8.01251180541323,-49.7433997672775 +19.2698521645474,8.50301311797118,-49.2001777015485 +19.0708847433179,8.50661746944705,-48.7678010047466 +18.5550648129223,8.35785966909296,-48.131014403746 +18.8926431379457,8.45778906778299,-49.017375957857 +18.3998003305416,8.31678282257615,-48.1707189638871 +18.7219413529536,8.4096764604496,-49.0398535028394 +18.327341214034,8.40737163363056,-48.4093295024987 +18.2904086974306,8.42078820785487,-48.0875132583278 +18.1123086849879,8.50813541301016,-49.837426676632 +17.7448413486183,8.37768134774598,-49.9738309147069 +18.5494656891797,8.578880642969,-49.6761590961492 +18.0788970015206,7.05812592843502,-49.3595993520749 +18.1654668410579,7.48009591435271,-49.3750325167496 +18.7862951364589,6.79856270120508,48.1322492633287 +18.8086046984525,6.43776019971847,48.7569010776664 +18.7227979827974,7.08946422094158,48.242457865619 +18.7014616156191,7.1780285488549,47.9003039974801 +18.7517972826312,7.23818076752387,48.6833912771068 +18.7532963186913,7.28506624828137,49.2877408125817 +18.8104147084959,7.47199315933164,49.8381540602459 +18.7717915351889,7.34522264338204,49.7891106088355 +19.2989014757869,7.66691637651791,48.5180411061118 +19.2405326035921,7.76454722474063,49.0214950976893 +19.2368105484893,7.90255731722751,48.5277030873202 +19.2034103067703,7.92763069435175,48.0462046529778 +19.2077811398815,7.96809380551318,48.538330341354 +19.2273546778232,8.02098583624006,47.088544514084 +19.2461728420033,7.99411039258132,47.6032078281858 +19.212465086545,8.01724198275263,49.0499295078416 +19.1662239465001,8.0913200673223,49.9624503472224 +19.2156046742555,8.04181384982382,47.5151768171071 +18.5698429415338,7.67410010586954,49.830330398656 +18.4796415290003,7.56170202958925,46.6723202973217 +18.4596440138743,7.48471459491023,46.5479288997828 +18.4713179899149,7.40735540985738,45.020700358176 +18.4811551138309,7.51307864752927,45.863597969581 +18.4833586065492,7.45718152970536,45.685479601562 +18.4588450919346,7.5786947047172,46.3269121525074 +18.419893075804,7.71972443060896,48.3322856920983 +18.3473288155705,7.77399378319242,47.6600285295575 +18.2544704794882,7.90211201116304,47.2268539322698 +18.150386422725,7.99086152721616,46.4488258623478 +18.0150198692068,7.94301243777398,45.7370540653965 +17.9478818959873,7.78814894615697,45.2177350533032 +17.8200444455684,7.22623696020628,45.5128895930276 +17.7032733232561,7.46867256318023,47.8964158999332 +17.6621444872094,7.6524163815303,49.534447750032 +17.5681324099508,7.80649032197906,49.9508865480685 +17.3280929767907,7.80791738892002,49.0802444563127 +17.2930306362233,7.71713621506122,47.4305452549595 +17.2702744370606,7.69490430871288,46.3388023627817 +17.290926399201,7.68397261247383,45.0117747004973 +16.3531050001166,7.93119662788625,47.6558992867039 +16.8621293619362,8.12682807750769,46.8882803534161 +16.6715941668258,7.82879329512043,45.9748791229741 +16.7882094182087,7.92739575117643,45.0525447390417 +16.5849464644365,7.71564325202344,45.7375208748912 +16.3819419435999,8.12942716076613,45.1082804707821 +16.4437321287654,8.11835446958095,45.1029670950904 +16.5304406965367,7.97100094967131,45.2774520008449 +16.0987447253793,8.03606251775906,45.5665590150584 +16.0290400544373,8.12837735528397,45.4487292933388 +15.9491130262325,8.10445581863596,45.2417162057584 +15.9550196249527,8.05121337140752,45.7066053381361 +15.9727088337757,7.93082291197385,45.0782211188132 +15.9810039262472,7.7213749200082,45.2856184499087 +16.046658906138,7.75297435578434,46.9628042244587 +16.0422558157104,7.72255723994002,48.979259688993 +16.5751912404226,8.01623147092755,46.376861867484 +16.5758123879862,8.06277604188702,45.0452002958313 +16.6457054741515,7.47194283051341,49.5694227702314 +16.0377953401792,7.69554055637471,49.9280637989333 +16.8797025078347,7.55966609300302,49.2871476821943 +16.4775756763222,7.4037439266234,49.5658616630672 +17.024711340985,7.70705100753738,48.7167257880047 +17.1285459418831,7.86142915246699,46.8871113950843 +16.9557227605268,7.91353198617822,45.4788244401497 +17.0298094276957,7.93829483751715,46.3582622689414 +17.1540334138371,7.94877196899647,45.1473919156684 +17.3395411583476,7.87816922542234,45.5825437307665 +17.5120763259907,7.85161063750904,47.3000553891651 +17.6369776544301,7.7953410660992,49.0511554537101 +17.7862585428313,7.76547110928819,49.2952590956988 +17.9535240076196,7.80342705430902,48.5365601504741 +18.1147174803639,7.75517884702632,47.9031520546253 +18.2710546477948,7.92098788360132,49.5677988141567 +18.2429373129489,6.99594909172034,45.6613616805348 +18.2381523784855,7.28590135504333,45.8537037169511 +18.2878913663843,7.06360357878938,45.7911263870681 +18.239777842755,7.61838200070492,45.0686669663892 +18.2406108778378,7.93087462878446,45.1997282586807 +18.1555943190811,8.26444292627115,45.1148587268798 +18.2514020433955,7.05300739421579,45.8268471436498 +19.06310427329,8.25440431299257,48.261588155398 +19.1044780136524,8.27239934884595,46.3519998270933 +19.1351643466885,8.2454257059079,45.8687590259856 +19.1684233517893,8.13800569580207,45.3587154204027 +19.1687576973604,8.05077667397426,47.5731791107661 +18.7756174494318,8.22534734345535,49.6728142827853 +17.9451305222006,9.24357028445364,45.3090149881596 +17.1995044324551,8.03477645359325,48.4312705680186 +17.091789708801,8.10905396994786,47.1913092580511 +17.2959961375452,7.72505995059692,49.8105949909398 +17.2872546942258,7.8687194888598,49.1703118624387 +17.8140994487534,9.08890779056021,48.4442818196199 +17.8149697149802,8.81221177872,47.5661543334287 +17.7453262276145,8.54141043529426,49.181528120035 +17.8072536489304,9.29959648454006,49.3098643216898 +18.4603115863241,8.03141539264701,49.2304535272377 +18.4413305247954,7.97765779032514,47.8108937512629 +18.4681424881419,8.05499609599888,48.5606027489799 +18.4702012254652,8.00962879733682,47.9207918210522 +16.9838387332889,8.81054906378925,49.9128421457978 +16.8307514929672,9.29358853832713,49.3806793518084 +16.7291520917162,9.44672423253801,49.3661712297938 +16.660440444474,9.47282146057989,49.1572614840044 +16.6642157802691,9.30117098527741,47.1413520411259 +16.6624215119128,9.40579862572744,48.1946774535342 +16.7060773151618,9.05494372342879,45.7297004862585 +17.0954269317872,8.4637620367035,45.0862110843463 +18.5243989339971,8.486716421551,49.8965002642434 +18.6015196853554,8.49898863536328,49.4215249812232 +18.6795749021096,8.55551994413919,48.4040697532859 +18.7265334194004,8.57526024194618,48.359061430132 +18.7508653129106,8.64812577614083,48.1913430401664 +18.8016827081114,8.66892475246815,48.9365369123838 +18.8392986850963,8.64305496724586,49.2040456384769 +18.8717725485781,8.63842789753717,49.1251336979642 +18.9010625574024,8.71395454474504,49.8163407342827 +18.9159720337343,8.64593907080731,49.7109085169001 +18.9072061303911,8.5738064339871,49.3437289613516 +18.8295323929216,8.49751064020797,48.6037607539968 +18.763776771857,8.35017947622621,47.1514835298437 +17.6684819665068,9.04190952468885,45.1329725968006 +17.7935512303134,9.11695106659465,46.3283704769742 +17.8994874239703,9.0799072086492,49.2479216766557 +17.8823882395465,8.79675052021123,49.7189601797504 +17.8099349258714,8.71197032926472,49.5958086141058 +17.7131501919199,8.77378689284885,48.8223325095345 +17.569392499593,8.80023569792166,48.3559240966034 +17.5343128684853,8.83997090445073,47.8941079113377 +17.6347890949149,8.78989386132414,48.5860720583268 +17.4150669466005,8.93343976868973,49.1364509573365 +17.2230055476264,8.91049344470908,48.5544805717449 +17.484358285821,8.91261939080103,48.6683468964114 +17.326643060831,8.89672890937414,49.2466951890695 +17.1054432139436,8.8860849933475,47.2690842333812 +17.0128045293159,8.90577755171648,46.9100344014695 +16.8863485600495,8.83974268733878,46.3577382857513 +16.6344078425555,8.82434658876615,45.4993439557538 +16.4877832139068,8.84622485393287,46.0084859664507 +16.3517490713755,8.87607721751771,47.1367924855051 +18.8095204716901,8.04430583630535,45.7018696302931 +18.8310227725743,7.96906340118455,45.196754097278 +18.8890075171631,8.12045975023629,45.3491022589029 +18.9160350518124,8.26105440491918,46.9352355715575 +18.9282396760743,8.24851491622035,48.3472617644575 +18.9125731626531,8.20664116021426,49.6984786724354 +18.8351963604901,7.82563094899089,49.8336268122732 +18.8317715571688,7.57798000207643,48.5223835096928 +18.8217709183472,7.5850330309647,47.5650812473964 +18.8810092713535,7.72717578527105,46.976989840072 +18.9865953173182,7.83855448757515,45.3799471618137 +18.7595217711792,8.21076437570512,47.2448281570468 +18.8571603175474,8.04019256440919,45.5527354858465 +16.2799229816475,8.97489828016406,48.7630801996408 +16.2017711371059,8.8533648580779,48.5688676334865 +16.2222209387431,8.91963460140314,47.7874895369386 +16.3390244396389,8.40136407037759,49.8882687311533 +19.0566479880429,7.98805237812869,46.3284988574474 +19.0344135064715,8.00380256316875,45.2804744266772 +19.0555287855574,7.97637739541975,46.0303761048476 +16.5069350235526,8.60168008507306,48.3066739607602 +16.4949086056511,8.54369115360153,48.3925950446247 +16.4997505989121,8.55399477351476,49.5406148856815 +16.3393404396945,9.80147849062079,49.8206927159664 +16.3729460541676,10.0151956813217,48.6199778260682 +16.4839170398769,10.0869051979829,48.0130026064315 +16.6184538872961,10.0780568196763,48.6710206907157 +18.8847269404566,7.9672963615562,45.5563500628958 +18.9159020946873,7.8859955242252,47.6697165029177 +18.9197330763548,7.86409957795676,49.3440547542979 +16.9190041821186,9.59323357768247,49.2615948033178 +16.7543549124686,9.90132887128748,49.1152555829305 +17.0446173368229,9.34544505598379,49.8399407047173 +19.0258686622906,8.02373720005801,49.0813237275688 +19.0125494056865,8.04916303385738,49.6878310797726 +19.0139136224931,7.95990273173468,48.1159325938531 +18.9817883077034,7.95997969687529,46.5083850325781 +18.9349696577015,7.99856550365283,45.1275518504242 +18.7236160711941,8.10964665956891,45.3289529637821 +18.6334003809827,8.36826656467506,46.4528976631632 +18.5804003070168,8.49054159284974,45.778374679137 +18.4140152884246,8.62435186067325,45.5551947280756 +18.4868318463635,8.6000849299632,45.9965799886416 +18.6287170323705,7.62211252077902,48.0694663405189 +18.7151617909621,7.53729219806721,48.8354441009059 +18.5552930386839,7.84087797258962,46.9137535983832 +18.813403230658,7.2810339130594,-45.7174308566914 +18.8241192053276,7.18818265113865,-46.0646999090753 +18.8066770837176,7.16403428548036,-46.3112472281611 +18.8274614458992,7.12140679440116,-45.8059060141908 +18.8533075721049,7.26168979340797,47.3609867034613 +18.915742829721,7.35448572012672,47.3333636359471 +18.9424908208958,7.35464696755365,45.7347518774465 +18.9704495675323,7.38971395504286,45.8385754204873 +18.9885206500653,7.50092070267401,45.3067643718766 +18.9373807328867,8.40383075058961,45.935111875406 +18.9063602997712,8.10383351634698,45.1167689013588 +18.9314871802421,7.92789806298193,46.1096265909659 +18.7540395206034,7.32648760217898,48.6777714703548 +18.9086063342575,7.30834526091774,45.8009238880481 +18.9812569656815,7.77844255321524,45.4268230301731 +19.0915878138436,7.92979977486706,47.3033407233107 +19.1211833529385,7.94876861624359,48.1264798085797 +19.2029828145539,7.89379355356865,47.2589024676605 +19.3247663622382,7.8641409381287,47.0949205145603 +19.4309248434964,7.8902389949,47.8273070672271 +19.5534797655073,7.83808611176478,48.5423653417343 +19.0485566946999,7.72960374252289,47.2807802794529 +17.4268663969816,8.56796416560331,-45.0521454348848 +17.7092724694456,8.45876022254261,-46.9055174374403 +17.6491560274451,8.54512267205192,-46.2754283883891 +17.5380980857059,8.57899854325449,-45.4671887507638 +17.7854092208121,8.45212535392127,-47.5653145107138 +17.8402082279244,8.46643437639547,-48.2924908900179 +17.8747679395599,8.48242630036598,-49.1291634633851 +20.4489390121879,7.83789100955184,46.0463573195899 +20.4788881731803,7.88331157430826,45.3888232146763 +20.5189728801417,7.63580271726077,48.6332049365823 +20.5776115727668,7.53305534743039,47.8422065731099 +20.6357164767333,7.50468082408622,46.7739942656013 +20.6810159607578,7.47250023471915,46.0967138587552 +20.7095123835007,7.63527456029251,48.015491356637 +20.6876241027191,7.63351953088766,49.2815962526451 +17.9796313790447,8.47460378896577,-49.8197789870142 +17.8881308801916,8.43838124233917,-49.2788082945108 +18.0701253499569,8.42882028385712,-49.579142582482 +17.8922265906775,8.51428723618229,-49.5090235995365 +18.1148444346074,8.38523414211859,-48.9939124688679 +18.18677990692,8.31714330927566,-48.0577853025564 +18.2403760737978,8.20699119083054,-47.0146146033223 +18.2849136939918,8.17959189397226,-45.2732063073429 +18.3688604785057,8.25205350385326,-45.2139616597456 +18.3760541646075,8.50783676844951,-47.6423071903597 +18.3838862381799,8.37330516964611,-46.9857609043731 +18.3791679905983,8.57384485499681,-47.7580296699738 +19.4653885430966,7.86001559669188,47.2049854913047 +19.5928393718625,7.65845257282923,48.2989752947551 +19.348851045308,7.95537268221807,46.9754714902751 +18.3909258714243,8.36116150853586,-45.3870503215049 +18.3957131906598,8.51601978054966,-47.1634500867055 +17.6710721172667,7.99167930527591,-45.2510814114211 +19.3118933564968,8.08343009477449,46.2964919612798 +18.6805101246523,7.91305561408655,45.9076308488183 +18.5348054125097,7.68529928749345,47.3205829371178 +18.4607421143528,7.42074608607696,48.9455772733939 +18.3827064736562,7.18291635175665,49.6257014344059 +18.4298458609228,7.31782104932089,49.7387063152906 +18.3039503856425,7.26773193957308,47.7160192985334 +18.2361588655593,7.30371246600078,46.5247968360638 +18.1842326249063,7.3402502444872,47.3052245305925 +18.3257540547926,7.26880154114041,48.7455906428226 +16.7915919414461,7.74295419147415,45.3214143115148 +16.7521120692538,7.57527620558522,45.8396817735863 +16.5240420819465,7.55724869296403,45.2633040365055 +16.6383183475353,7.44990971995902,45.3356415617571 +16.7287363747025,7.40248257997442,45.62683150547 +16.4406051765643,7.82248395426587,45.3643813821992 +16.3143057633454,8.04896069773487,46.6201853349776 +18.0634657506824,7.33653605817588,49.9653260304116 +18.1485075978351,7.35812867739757,47.2238836990195 +18.1126521312641,7.39508148252337,48.7207861031221 +16.1960257866689,8.2334696026018,47.5365318946702 +16.1078364464326,8.33520810094284,48.0655004039355 +16.0266757134595,8.38538204593683,48.5603915412937 +15.9796786425216,8.39735736966315,47.9901253902265 +15.7629271846111,8.29448546097326,47.4674774914302 +15.835543514939,8.31330561511387,47.7184516214633 +15.913339172867,8.35498662225485,47.9689203671853 +15.6804749760259,8.34635892923101,47.0523365804981 +15.5161901483715,8.65563413659036,47.2858033661398 +15.6231370616245,8.41795811570109,46.6468031025627 +15.5721814360558,8.55230909207982,46.6064062867033 +15.4953161531717,8.72471719356483,47.0606109251921 +15.548177394711,8.89548315288244,47.3161552104652 +15.5867263383189,9.01159478176015,48.5429798561668 +15.5208460648686,8.84618937119377,48.1003939045212 +15.7423486749692,9.22484994966987,48.0151546676144 +15.6484771442117,9.05963102403163,48.1568583192968 +15.8590417440009,9.11697349010911,47.9841396354165 +15.6706112613175,9.13864096728637,48.333276966252 +15.8833161673136,8.95762994823192,48.3715831115375 +15.8137342971656,9.18606230081308,46.9715464038439 +15.9242922396704,8.69753652780497,47.6087749067084 +16.0711381461845,8.47267724914594,45.4588897389526 +16.189618254418,8.49380362025541,45.473949374698 +16.4934727200188,8.70693419070591,46.2468295239594 +16.6160052627391,8.88368237518873,47.6043465350789 +16.6779465862168,8.90956728949303,49.0030856117287 +16.3387339159899,8.56809409938254,47.3738244258452 +15.9729825236446,8.5783387652674,45.6206804704115 +16.657706224506,8.92728711884423,48.1497200293033 +16.6663712733638,8.74112203504443,48.5813598040091 +16.7171179502981,8.65603442555756,46.7587578649106 +16.7392964907396,8.6333428762235,46.3279704582364 +16.6574249174185,8.83036971264592,49.0387819427153 +16.9385249022377,8.69375568274978,46.80983635752 +16.6935007550588,8.70940130964116,48.11293956031 +16.8285124456574,8.69780397779898,46.7407688656152 +17.1544356470843,8.68728615355326,49.4512339785556 +17.0402586981094,8.72853596186125,48.0549632756537 +17.5038576513966,8.24410156862774,49.1448859647231 +17.4520195796818,8.33620803061221,49.2665731212664 +17.3964535613105,8.24076278877933,49.3419007869189 +17.3451780475099,8.14262088351957,47.7401876581527 +17.2832194506303,7.98412456137755,46.3009647160912 +17.4250897774652,8.35023800114306,49.8478758309228 +17.7321412013318,7.75014843765254,48.4461577172087 +17.8099127747883,7.76634730231063,48.3255667750771 +17.6907777113776,7.80138470863081,49.8447634529318 +18.4300673378992,7.73967610200021,49.5642854245046 +17.2661649956889,8.09492845289782,45.5097440796505 +17.3429573391758,8.15383090462632,47.6549861404098 +17.4481849959667,8.12718416987319,47.3397302451191 +17.5095084064236,8.22562908130804,47.8563601137428 +17.5353190445631,8.3050431658163,48.0854374837733 +17.5959375803188,8.41535642376734,47.8004633586372 +17.6303946214949,8.48697161722222,47.1883118883977 +17.6647047286773,8.44418128839959,45.7289628215352 +17.8694585753106,8.42356847031084,45.3069136032917 +17.7458291189926,8.46931058554478,45.8731799588701 +18.4796593239645,7.75737232775097,48.5847917929669 +18.5634877510514,7.82625783514163,47.4195858637279 +18.5163696771271,7.89319347138544,47.0464566968779 +18.4619520757373,7.98513187718065,46.8146301992961 +18.5653317194316,7.75106439074203,47.8682649526079 +18.294598875245,8.33465650391506,48.8001432807796 +18.3877096734177,8.18897512400567,47.8945383910218 +18.0409657435443,8.4009468445848,48.0684737113301 +18.1752606945936,8.32617364858884,48.6128049801767 +17.9400630199077,8.38652980030371,47.0211431187132 +17.6421645983634,8.56827518264668,46.2192390838453 +17.8297709130124,8.45455917451876,46.3001775364207 +17.7320963526363,8.54097945959864,46.1310524914317 +17.6061092124279,8.58199686780671,45.3611984493303 +17.6319709477859,8.5933746801509,45.925228300761 +18.6229974292881,7.81923329090582,45.2753494848281 +18.7435875401102,7.7052195941557,45.0452170260261 +18.8417508910624,7.56591842416955,45.2401752840217 +18.8691830383787,7.45804033178679,45.1925353265459 +18.8383436759474,7.37821050032397,45.1902830798727 +16.9033286553066,9.59593795067855,-49.9514883077027 +17.0010142509677,9.55417216931483,-49.2100379760466 +17.1260037136065,9.5399351346972,-49.5832369646504 +17.3172705030145,9.64949432211858,-49.1329571284312 +17.4448028916917,9.73835134119654,-49.1343112760288 +17.5257419388149,9.94410050803229,-49.0415467984255 +17.6603919913836,10.1662305846643,-48.8458342655678 +17.7390923908657,10.3102224950438,-48.7127886787871 +17.829982986947,10.4480653297457,-47.9254080101902 +17.9151410524701,10.5241695958831,-47.7479702379961 +17.9046530174075,10.6273321794856,-47.7770988986109 +19.1801560813326,7.25424879629056,45.2695534214685 +19.2993027465872,7.23748121975418,46.5136216614535 +17.9044765958853,10.5916752789456,-48.3102281857577 +17.8521137337197,10.6741005012057,-48.3374428864773 +17.7377902702892,10.3378902396627,-49.0146682885078 +17.7705694277677,10.5209264961495,-48.8777729621328 +17.7292486453373,9.95270887152621,-46.8431665365677 +17.7281230196399,9.91316467461732,-46.9239825193167 +17.670903737101,10.1235787092815,-47.6426666484043 +17.7959559247837,10.640201577591,-48.9578394201668 +17.6708161209292,10.2989694633142,-48.359585020776 +17.7239051132284,10.1314084392605,-47.9611019320979 +17.7220269598169,10.6008739960167,-49.123241301498 +19.3993319961447,6.91677367712972,46.3312974289958 +19.2131985029057,8.0478915376192,49.699962592106 +18.2976190076619,4.34117198857866,-49.324406457787 +18.0652586670643,5.93448592937496,-48.8825884157422 +18.0690203104426,6.09739648436169,-49.0024312726121 +18.1165533620887,6.46056494682478,-49.1259612884119 +17.8960192021392,7.96543846816544,-48.9492155647518 +17.9729356635434,7.79615927110016,-49.5368204889367 +17.9198230613678,7.82030891407395,-49.4652986650396 +18.0322776697559,7.79364344838402,-47.2211949133746 +18.1257014562047,7.81254756785736,-46.1551626440288 +18.1635977985512,7.82359503263756,-47.9494013112459 +18.2289262147585,7.73435275537996,-46.9211836950487 +18.313205552464,7.70213482772403,-47.8126476260652 +18.4058518614517,7.71683607489561,-48.2045401827198 +18.477318936149,7.79688600355062,-48.0035976463536 +18.5157561904409,7.95994316305205,-49.2029770356547 +18.4887604648445,8.14570792127048,-49.0412715306913 +18.5105318084398,8.36609092954561,-48.1888443045098 +18.5459244134303,8.54408815783207,-48.61125562548 +18.8620366583739,7.52671795450231,-46.3057268348772 +18.8984734525562,7.38459073587438,-47.5240512207282 +18.4975988074429,7.94761035875656,-49.47267435947 +18.4495553611952,7.99874388833428,-49.5264486285553 +18.4198244602103,7.91260108700267,-49.8555266109283 +18.3894760650122,7.91648214640713,-49.9627230865599 +18.5215625372552,7.99937813843953,-49.7535607400222 +18.305116705519,7.91190399803886,-49.9496979985601 +18.1251596392867,8.68715149613055,-48.986438292179 +18.1250433677831,8.61296008576518,-49.0373552357808 +18.1065615216137,8.47007888504882,-49.4357309251518 +18.1412927143492,8.51224055251483,-49.8353055241119 +18.1141737628146,8.52466554132177,-48.0153745559687 +18.0006159764561,8.60554238214944,-46.0053628753402 +17.7866603671741,9.18479206711742,-48.5058648065906 +17.9016545789229,8.94364927756698,-46.3514247288567 +17.9507406665986,8.75906105872256,-45.5118210416258 +18.6174717991192,7.40629549655781,46.7108220681971 +18.8705237306564,7.38042371816016,48.2987178735654 +19.429725532778,7.72655437134984,49.3424034273502 +19.5160652645676,8.04402075289389,49.1217850470924 +19.6136120429605,8.19735412180904,48.3789219769168 +19.6858298889932,8.32040975174841,48.915962477712 +19.7818380586522,8.32909167440383,47.6324527190121 +19.9078891323081,8.2611789815105,47.961229405667 +19.9247007412387,8.07676391316338,45.9417219355587 +19.9326155803147,7.98544230533101,45.3980602141842 +19.9537478291505,7.88876277078846,46.0508217857717 +19.9419162608851,7.86598278550965,46.4469369621238 +19.9542565340411,7.79869415106063,46.5886401941388 +19.0717066813932,7.36881286794472,48.8132725762466 +19.8690085199978,7.90146904929908,47.4685637765338 +19.9259204653203,7.86418775301711,47.2197569250134 +17.2614194482497,7.1148223058313,-48.8347926567492 +17.3566960976888,7.07235683427302,-49.1334782225099 +17.0602350255969,7.4436020053772,-47.9310333340867 +17.141216924078,7.30144197140083,-48.7880485032815 +16.9601292736282,7.80879699976325,-48.3525156687697 +17.0287922906859,7.63513899550056,-48.3797719529741 +16.8806638326336,7.85623051966347,-46.8306812057741 +17.0428704134823,7.78787034423739,-45.4252637541497 +17.6600901367921,7.45333884168561,-45.1319723052079 +17.9195244023693,7.90137297121087,-45.5310986244903 +18.0216085587148,7.85140589695806,-46.1965868450425 +18.0568563983832,7.45447778718751,-45.0295433725071 +18.1395604559202,7.25940302673618,-46.064505012861 +18.2885980964182,7.01916360465491,-45.5235440413818 +18.4007258264159,6.9403049723386,-46.9660237571788 +17.9779976635566,7.90585450306735,-46.3749813746738 +18.036072830909,7.6270352203448,-45.7676822439282 +18.440209332943,6.89286278483594,-48.7663542874959 +9.83019383253382,5.53771526961454,-47.0069848900719 +9.79930719248391,5.54065472561463,-46.8185424373723 +9.68395491418524,5.6063071447144,-48.2457493321639 +9.6424684587909,5.60304213519508,-49.3620331719105 +9.71158042783583,5.64054582339201,-46.0487362560892 +9.70853599834536,5.65320620771891,-46.7726019524058 +8.3488424354918,5.66578434907172,-47.3183684323831 +8.38074310801547,5.62333318018339,-48.6906148348289 +8.54844786658728,5.36598162126005,-45.2581662745588 +8.59330000554199,5.26941654153184,-45.1382193878216 +8.61457312518467,5.25128141559492,-45.2140872833154 +8.69050506843189,5.62503788983567,-45.8655299517104 +8.67939465471084,5.58865058794956,-47.9534617986932 +7.65157592342335,5.32808756958846,-49.9823321374483 +7.5596922127718,5.25076852924822,-47.5269072624722 +7.62128939957241,5.28250252593249,-48.3225141342566 +7.53491624790614,5.26207094364365,-47.9858046443017 +7.61264047582191,5.24887089936122,-46.4378616297002 +7.44988434104492,5.31985992565054,-49.6198195604821 +7.19576119131829,5.40023773188601,-48.4027476278737 +7.20349519530676,5.4334450985807,-49.6362389640328 +7.1985106918336,5.27430283573835,-47.2822873225113 +7.26282968661698,5.24139631405578,-47.3295172895749 +7.1824578843953,5.32506268109903,-47.7183412101907 +7.30945793340172,5.20987424643864,-47.3399364785108 +7.61940843287201,5.45675711889046,-49.2615508697843 +7.55697040242556,5.36142976880803,-48.3051577808701 +7.41402806787331,5.27753824683793,-47.580646708387 +8.02519328243236,5.71252970954105,-49.402569392474 +7.93797997282096,5.6729791751583,-49.51095719514 +7.90321800449355,5.64659716626328,-47.952620977647 +7.84660908010412,5.66230258724077,-47.3562672214878 +7.80624450542557,5.53314769227047,-46.6389837333026 +7.34992062178332,5.37796173533229,-48.2063237022498 +7.3616680926529,5.27040982735946,-48.1853800089051 +7.3402862760378,5.25261549110401,-48.6384825652865 +7.3839391085084,5.18007822317821,-48.908804550735 +7.37378932636905,5.38017578875085,-48.8083167687216 +7.39489704355839,5.18409088119399,-49.018406767836 +7.42804187805693,5.14740564107352,-49.5786413661041 +7.45560688028398,5.18671555185442,-49.3158043395925 +7.50359081395535,5.13670919228484,-48.012399731877 +7.54172552842344,5.1065529101125,-47.2752047831678 +7.70491316506201,5.01380936638544,-45.8062954098457 +7.47398832722932,5.16386415969719,-49.0056606106796 +7.82311895740682,5.06225324608908,-46.095560756444 +7.94746518365484,5.2258824022827,-46.9020078180785 +7.9945793462392,5.31062149530016,-46.0304847714565 +7.91286822667338,5.15323513287615,-48.5138388981715 +7.89455771379061,5.10790203324973,-47.2326657149183 +8.77022282405173,5.19785998691845,-45.6796258599606 +8.76459808843128,5.18639730558583,-45.5191662916713 +8.7612540064742,5.17587753336294,-45.4108638232349 +8.78034170495398,5.20896424178588,-46.1881438862577 +7.72337194190154,5.39784314855519,-47.8392438940888 +7.89697796663587,5.38721388447792,-49.5825025235823 +7.93506667554267,5.35592453025127,-48.0792859066889 +7.96863574522875,5.44209795916721,-47.6542788486503 +7.9420401661918,5.39112914453573,-48.5308491727388 +8.01634736881377,5.41452560091579,-46.9086452843991 +8.18053576870203,5.58519853927467,-49.6442603878376 +8.17901149130255,5.62802124106941,-48.6362454575869 +8.20085741168094,5.58949225842748,-48.9589209632575 +8.17364264970873,5.65823538457711,-48.5085631365369 +8.16272680345926,5.74918100497797,-48.7524633972289 +8.12138444299878,5.76427388995811,-49.0983190791018 +7.99793719393365,5.43602205157091,-46.8356406412358 +8.16493444121704,5.47333094009399,-45.4767911480152 +7.97683119197996,5.49247252563374,-49.3425358460975 +8.18811668828559,5.22683358001271,-45.1014051259388 +8.18772743160483,5.3391401153434,-45.6615652650079 +8.18974354921523,5.42590306926787,-45.1182637677218 +8.1159002181775,5.3370693422998,-45.7428280687492 +8.07448203386416,5.55861529724065,-46.9308405613925 +8.01194793157118,5.75912319134217,-45.5562902115872 +7.97107441882,5.80999736561422,-45.5735412452563 +8.05976260040495,5.70802247748008,-45.9487852265121 +7.87164245515737,5.85617446849715,-45.4635761862032 +7.84340721845474,5.87124137865112,-45.6539767818373 +7.923700136174,5.80372742345806,-45.3919596247721 +7.84880780922248,5.82776850610722,-45.7947965852023 +7.86861030834834,5.81223185282103,-46.1874788319024 +7.9140938401245,5.64932494057874,-45.7339270483054 +7.90214959220658,5.7026281589426,-45.3967843546078 +8.10535310922953,5.44007116635514,-46.5784038630997 +7.9073461848207,5.57555789300229,-45.6462837192968 +7.90844574235997,5.59971262811706,-45.6899807956983 +7.88913324913988,5.56622322320875,-46.2702903338761 +7.77210255617774,5.47477278830376,-45.5518749769659 +7.64259774782683,5.37415571079203,-45.3544447020119 +7.81799935657975,5.50116277987459,-46.462394092998 +16.864521911963,7.64300214404055,-45.4789853183209 +16.7282901791711,7.68344798507281,-48.513257480536 +15.7520098458248,8.90115286226416,-45.1569373956836 +15.7989174062796,8.84143216002026,-47.5441926743967 +15.7872919395163,8.7796053349127,-48.913512057526 +15.7989654552103,7.9312573226754,-48.5927377318376 +15.7291089828046,7.90917426904322,-46.1647172517591 +6.69984057899814,5.77900802248917,-47.7205030826358 +6.7204947259574,5.45439386568976,-46.3445824958255 +6.69118658665685,5.80890544184976,-45.5190140641895 +17.5454636637313,7.89319762793482,-45.0618588127217 +18.8602294318183,7.6923571196591,-45.6730965641587 +19.0146089197826,7.65682719483446,-45.884820559005 +19.7165914634002,7.9988561841145,-45.6298732554693 +19.8657633465311,8.18510049303408,-45.1166951950053 +19.9875546830137,8.36618603063659,-46.4144985138894 +20.084975104082,8.39566003071771,-46.5407024824256 +20.0521223022008,8.39993873831887,-47.7216226067236 +20.058740981593,8.30847030849278,-46.4157341616646 +19.95995158059,7.96868869264748,-45.0672130491585 +20.0132215931561,8.0799025346907,-46.2062250990216 +20.0162494353035,8.2191127113398,-47.5520155272619 +16.6621144173262,9.30040926957844,49.8782488091779 +16.5791575817787,9.31503054849937,49.2081688635649 +16.5225459945516,9.41621996255135,48.9824799163251 +16.4556037822175,9.4633671011932,49.5920127419864 +16.9400178979915,8.67758731427324,49.5001069428027 +16.9355074392202,8.64088523323241,48.7101629712787 +16.9187543976224,8.4341463711263,48.6702616055658 +16.9453275856852,8.5807959050995,48.0494107816315 +16.8834297283507,8.32036216908003,49.699986168666 +16.9910518067335,8.65670079662962,49.6221368640944 +16.9770469109972,8.74185747197453,49.1553010308999 +16.9843817893427,8.84573987822657,48.8309522234713 +17.1061330409923,8.75366697651631,48.5374877417884 +17.2243965507401,8.71068179645565,47.9444871279938 +17.0363606641618,8.81963389356322,48.8953583281436 +17.3759321829685,8.6210034578493,47.3166392678839 +17.4948321939602,8.56489233469303,46.9217261949992 +17.5819096066449,8.52830157988606,46.1991661940187 +17.6856258408438,9.35102154517361,45.7556363754048 +17.7002573089086,9.27201401379019,45.0813267355664 +17.6936634230491,9.42208048279353,45.3252727446346 +16.3739355552217,8.23438228017551,46.2346997517194 +16.3351135614904,8.33686902669854,47.6232988549856 +16.32045771727,8.49195529093201,47.4093381109922 +16.2744136217273,8.51900696001894,46.4196748588677 +16.7836937442402,9.05991210066214,46.2593665927594 +16.7162720511334,9.07675171514024,45.3845795657583 +16.7830401272764,9.03726290385649,46.8382364673902 +16.7933251843894,8.7892571075694,47.7842526787208 +16.8394728976047,8.56814561582533,47.7412525266045 +16.8711707105938,8.46788183253822,47.8788626023436 +16.8955464505035,8.44136738660428,46.5749721882398 +16.9162249636647,8.51906039677111,45.654808089515 +16.9045340717445,8.66922180244865,45.3129482538443 +16.6362793527361,9.13768628709374,45.6417919854126 +16.5170456871322,9.20357175821793,46.5922101713112 +16.471411377929,9.22051020313417,47.5573035433722 +16.4502724499663,9.12753436959204,46.7230491904356 +16.4092841609515,8.96595196367214,47.2104622012864 +16.4270473790113,9.04203691467713,47.1434386523799 +16.3967962268512,8.86652415068582,46.7095486605838 +16.4141720657846,8.78314184112676,46.2480463049504 +16.425393589001,8.90074207871259,45.9296590796061 +16.4256245559056,8.85912870916014,45.7636983217325 +16.4070199140346,9.135211792599,45.566636226161 +16.6619193794469,8.90923582074543,45.6211547495448 +16.7336778183949,8.80953297497701,47.1010494864794 +16.8218304887474,8.79912448467926,47.4465892876858 +16.9614932172719,8.87234611156986,47.1564723210636 +17.1514731073608,9.00370092557773,48.2269964408098 +17.0376755687733,8.96208916597804,47.5680605173364 +17.246998078191,9.01616552658641,48.3350927975813 +17.2993925046657,8.96371318167879,48.2790799767615 +17.386366937104,8.89334649844286,47.3622228426478 +17.5553576777018,8.74827035916408,45.0195562238146 +17.4725978615571,8.77939393080942,46.6796601651654 +16.8878638930804,8.8344069840675,47.643037113759 +17.8097016328392,8.82807204044177,45.3628257883285 +17.8730298218842,8.79751130127315,46.5065719864634 +17.9488678347584,8.77217211391539,47.3770580125588 +17.9616925823721,8.83755302924138,46.6520456522203 +17.9841754296887,8.83556319052526,47.3655252761759 +17.9757612626212,8.71730844602938,47.3984332706995 +17.9547587137237,8.74883859932787,47.4421990721174 +18.0000037478721,8.89191826926042,46.6930752632303 +17.862153849917,9.12931228595876,47.342678912457 +17.8937456831289,9.40856457092314,47.8216792700786 +17.9074372478203,9.73944117758934,47.9994514535233 +17.8898795734752,10.0836176664171,48.8060289075266 +17.8674881367284,10.4021169110336,49.9565418665454 +8.90402686037091,5.78244927224917,45.8231389922465 +8.92816209561903,5.76083082224963,47.192429779642 +8.91768914168061,5.75073403971407,46.1660065317234 +17.170149990388,9.66460435040453,49.7556716355878 +17.0603842267576,9.40975466753146,48.6221795766264 +17.0285278216817,9.08297180739103,47.8620015169445 +17.000682532252,9.0010034368014,47.3868988590212 +16.9354235370705,8.9981347738526,47.1654353882133 +16.793752468028,9.2284993132164,45.7886006114425 +16.8862294353369,9.10637023014106,46.318145485371 +16.7314762850119,9.28066213575686,45.5111781638901 +16.3476009819018,9.42785941067061,45.3120683189206 +8.95159525596318,5.73069714235584,47.0223452631926 +8.94706126647147,5.7382650026699,46.890853592272 +8.97702794732113,5.64206337134487,45.3561743609555 +16.1820819378835,9.42938565723011,46.2882424167387 +16.0369088591656,9.39663503145252,47.1885979795892 +15.9094020144248,9.40656795376168,47.1445199480797 +15.8360818380725,9.43370571589204,47.2160967117323 +15.8645953555319,9.24918599365574,47.6453132080921 +15.8925575636633,8.9204996924713,47.3981270122703 +15.8589726242875,8.64812831244415,47.8975693747669 +15.7688939556627,8.56783449596552,49.3480547765448 +15.8166965885792,8.49572536550173,48.6320195098222 +17.1695392328361,6.50247493833555,-47.0019765181057 +17.065674455705,6.35671604711733,-47.7300939064469 +16.9347419860539,6.25960392682935,-48.7247671265525 +17.3541946364347,6.48968050842285,-47.5799816331852 +17.2419937090054,6.5077879027029,-47.3225431102447 +17.497171573385,6.698140708778,-49.4306580864989 +17.5719115154382,6.93235772268245,-49.7510813691423 +17.4226078273464,6.58090067768306,-48.9741903089735 +9.49615441593656,5.87545330768419,45.1299346770197 +8.85342185632659,6.13058532046335,45.2248706861884 +17.6405011637761,6.35029220284667,-49.0995689595105 +17.5329627341457,6.41786281002016,-47.7575306401491 +17.4441448588091,6.55112189035302,-46.7887836549302 +17.3024534271966,6.65014075459124,-46.6076202427451 +17.1160111021021,6.69806961057472,-47.7328443471454 +17.0489067649389,6.69067244806886,-48.5677179801185 +17.1835216373413,6.67872150066199,-47.3239609373475 +16.9664190653758,6.64871139498264,-49.5280079429475 +16.9194560518215,6.54701002993352,-49.8521317352878 +7.63933758969527,5.81696655723019,45.1961801528237 +7.58799391720165,5.71911759489828,45.5422953309707 +18.2732882583499,7.99521675835314,45.8329363982841 +18.2804229713368,7.83009472196976,46.4864020175444 +18.2813529213333,7.93220773446762,46.2609709637721 +18.2913834821129,7.79494318764199,47.2333597657019 +18.2940247264505,7.7210309761875,47.0476939068442 +18.244351058226,7.7520354603564,47.4590735026133 +18.3370078869288,7.71780113196372,47.3928327676542 +18.1453543163806,7.74046594020335,47.3756269596084 +17.8596236158433,7.5673760953583,47.995720170486 +17.9927965750358,7.65425778114167,48.1056804962717 +17.7641169394033,7.44872009953586,46.655770358287 +17.6546832093371,7.42273823074194,45.738971723347 +17.5836589474274,7.34658463686454,45.4984310915475 +17.4744736321637,7.55447289319279,45.0042297112915 +8.07603270354604,5.73111641009831,45.5733798533302 +8.0385681432572,5.7225007767836,46.0241920413109 +8.05919188185742,5.75618078346229,46.7857344970312 +16.4079737243669,7.08745495245164,45.4906501554883 +16.3838188719333,7.15662540286375,45.5426325124143 +16.4243960881795,7.28088322165582,45.6870798172711 +16.4596082554759,7.4679372138843,46.3944748795543 +16.4638374527122,7.70173308163512,47.5529540585032 +16.4762580731086,7.76269893560209,48.8797792580353 +16.4592511666499,7.76842089198492,49.3780707825024 +7.29660675437847,6.13528742296888,46.9366740272559 +7.23227369231814,6.06016276218423,45.5191874008977 +7.38436813127461,6.12893711933349,45.0438647332983 +7.34704978059985,6.15805091408042,46.2983398276359 +16.358217715199,7.34665691026685,49.0803306037831 +16.3258701810136,7.16721990707987,49.7615530268171 +16.3536736102318,7.6021138740647,48.838020937063 +16.3440315871693,7.51436648427446,48.9730890236681 +16.2783818928731,7.76813459446593,48.0451842407194 +16.2185443026988,7.78941189056565,47.8507884581714 +16.1266280296999,7.65533496225255,47.6334701585458 +16.3018920015388,7.76751889643995,47.7455834496438 +16.0581983519617,7.47101505255579,46.8714694542564 +16.0698138945532,7.56603119848688,47.0499295310221 +16.041829786529,7.35978106092267,47.1103452236092 +16.0930410605521,7.18018302526509,47.1349059337874 +16.060613927548,7.3399630895484,47.0742532351355 +16.1634140699421,7.13160410037434,46.7613064246864 +7.33565271221254,5.87271464091435,45.9848910546694 +7.3182631720783,5.74017166751565,47.6672091295379 +7.35862085603699,5.6447375469041,47.6229874169853 +7.33915630023626,5.72549512358784,48.3813460955466 +7.3556180786452,5.66655460042504,46.6641742724186 +13.5398671071008,7.98278541420885,-49.9587512912129 +13.6077937569813,7.9033846994001,-49.7601429918021 +13.792483433344,7.79153369716775,-49.0547561702845 +13.7248306418194,7.79288042861725,-49.5209429933458 +16.4090083642652,7.05212858551344,46.3343066260552 +16.2790090209823,7.06101203868674,46.0175039851666 +16.4597602127162,7.14865089773915,46.0810273750866 +16.5177459389786,7.14439680250539,47.2313532343587 +16.5618979388441,7.32061995523641,49.4446764294174 +16.5333669594967,7.2430992536796,47.8949899573341 +13.9097809201973,7.64468335636009,-47.3938053904162 +13.8196551124975,7.70117499407393,-48.4569630411898 +14.1985865196338,7.68967455816241,-46.4646584300192 +14.3860126037784,7.73773948441509,-45.8903057389923 +14.5788799410932,7.72574909537257,-45.8172174242169 +14.7398289380379,7.70960395427972,-47.0978966663526 +14.0526101568196,7.74503969719928,-47.6258917897463 +16.8669128090349,7.30742730984122,49.2527164836702 +16.780540557126,7.18603569438236,49.164076311361 +16.694255478664,7.15372441504089,48.2066790309066 +16.5734250809732,7.11753521706458,47.2943466840471 +16.4796159485621,7.20213613838538,45.4117236523895 +16.0953088193717,8.44075201236545,47.6831296666864 +16.1296071558997,8.50270679063541,45.7429124381895 +7.91637673367003,5.82145113481071,45.421368246684 +7.99218431192527,5.74524646693758,46.5649893464867 +8.06729893475309,5.69886307494697,46.0483437822473 +8.14043396381199,5.73008989468248,45.6139391799814 +16.8782111893509,8.54352365703406,49.8524414831745 +16.8394909229534,8.73883988309952,49.5381265414599 +16.8217108059989,8.83171125848717,48.9774092967174 +16.7959483181914,8.77106212114398,49.8489511348951 +16.7070929739684,8.61181341962453,49.6469283784768 +16.6533923252988,8.4218925293609,49.423276390322 +16.5970121923132,8.31044840449695,49.3257912643886 +16.5576751698125,8.20617070410803,49.1415408671718 +8.41645513819117,5.51678715274753,47.0699017838807 +8.39971545086299,5.49865864920349,49.2050651591594 +8.40972976291591,5.58267216924335,49.7785456293798 +8.40480836950519,5.60798607815343,47.0952743928913 +8.3656342230598,5.60981184286284,46.370902263866 +13.4192286987666,7.73724664313234,-45.1356598822913 +13.3199132253122,7.67310117484653,-47.2860987539767 +13.2717104370725,7.57677549063446,-49.3803636305352 +8.40016345010835,5.60488095192329,48.80133180345 +8.35661993551843,5.57762920936074,46.1476510710673 +8.30733189221197,5.5790582448233,46.5191395624108 +8.27068636315442,5.55966886794734,46.2446772766427 +8.25877089551422,5.56156514605583,45.8720537826115 +8.20097631707565,5.6470749618696,45.3030198699161 +8.05930420879713,5.84052395555282,45.2892192390526 +8.05409108129762,5.91644249592589,46.2673379118525 +8.04701895505776,5.95392045855286,48.033011355547 +8.05908653405733,6.01163843377996,49.340079506606 +12.9738278329582,7.61925479712962,49.2575521680932 +12.9448494072378,7.63582330217813,49.9789093125154 +12.8970915963334,7.61978156520994,49.7700245667619 +12.8189636889306,7.64335002764466,49.0668863891774 +12.8392248142291,7.69513756823613,49.7453085746952 +12.8288328715752,7.68483807078393,49.7953047301512 +12.7737729312455,7.69598302195215,49.6662398546665 +12.9905747297362,7.43690546891267,48.8473461958168 +12.776411850679,7.76905243956,49.0692858061785 +12.8474630553458,7.78817042801006,47.6843864822954 +12.9714088772622,8.08829627452603,48.9765980704348 +12.9079843153464,7.83856473801495,46.871399460171 +12.9421575412249,7.92391523600268,47.5226093082463 +13.6757624863271,7.69301821595405,49.8279545350042 +7.91161625515167,5.85716576326334,48.5827904168997 +7.88015453389696,5.80663745062185,49.7609533651475 +7.93918010498466,5.8608672050228,47.694494684308 +7.94345434580921,5.96987865010335,46.4516955002187 +7.92335874807355,5.95012337349512,47.1560637695649 +7.92147644300468,5.96688831066361,46.6677052512001 +7.94497910605406,5.9832862478791,46.1459995138088 +7.92738512652139,5.89391597733962,48.277311824441 +16.1581575019681,7.44974729028351,-46.1407266426184 +16.2087807247828,7.35774530502808,-46.0985560225838 +16.2658648670859,7.39519317164007,-46.0305595117775 +16.2875540530893,7.4986591491977,-45.7277668692047 +16.2510788049127,7.79372876940819,-46.5453612077901 +16.0981068343137,7.58406514588539,-47.6091299717858 +16.2025963963702,7.70140756252944,-48.0521836642686 +16.157998431717,7.66302283313731,-48.0296125858272 +16.0356709373732,7.54179349199736,-47.4574257467636 +15.8957300865972,7.37838952668517,-47.7127832515911 +15.9758415698796,7.51317783460498,-47.2739592035498 +15.8348673323442,7.36081795417261,-48.1850592981185 +15.8010259517757,7.33864147438807,-48.7897986076513 +15.836028821672,7.3987871722468,-49.5122781151707 +13.7046622797121,7.60146897589559,48.0186124801875 +13.7353434833208,7.52568341105804,47.6304710420387 +13.8363540053121,7.52172123967235,48.4540499904865 +13.8106823324007,7.63058299207113,48.5114225617896 +13.7640799689316,7.73682316432167,48.4778884597995 +13.6882203531202,7.81984909849172,47.6070991224233 +13.6701080337813,7.85391614549801,46.6612303145057 +13.666739937926,7.82269837401608,46.2399371087193 +13.6374212596326,7.78387605045478,46.0851879085862 +13.6192715642306,7.72524708396608,47.0941046902541 +13.6048988073206,7.63363577572913,47.2952041895724 +13.5882330736397,7.56625904680502,46.7949570780769 +13.7066848542674,7.55194792414258,47.1755695319621 +13.5889686299988,7.59629574897721,45.5429238529211 +13.8047218850924,7.49989607086925,48.535743269588 +15.8627730903266,7.40713369853288,-49.7130717522137 +15.857473319619,7.4173482038765,-49.7883756170227 +15.8769477580638,7.36062545500818,-49.1481281580834 +15.8721103273481,7.30151252132956,-49.6946410508397 +15.688634471894,6.88509626891183,-49.5079311883982 +15.643895248759,6.96523942435365,-49.008564513825 +15.6276864308611,7.05952224056294,-47.8658103707302 +15.8490780057181,7.41724922089041,-49.6188394525073 +15.5961783294532,7.18695681921759,-46.5419357919151 +15.5782546766971,7.29498210960582,-46.1921292660866 +13.5359557147257,7.72852405929461,45.3269837259006 +13.521381857106,7.76128517487014,46.0150913868675 +13.5195579566843,7.68475370484083,45.4688689691725 +13.5131259760528,7.78327038623414,47.9184437346466 +13.49829131278,7.75194688107356,46.8829609582697 +13.5503573343579,7.80162816402057,49.0062789947409 +13.5696325567854,7.7453308469604,48.7010284080039 +13.562338864584,7.63667165539349,48.5205336362157 +13.5411890572888,7.48130719945304,48.1670081560069 +13.5348735384618,7.3660395832513,47.9179542880249 +13.5321217723504,7.192018439696,46.3612791340379 +13.5573879776113,7.07542946860829,45.5641374137362 +13.5847041762812,7.05494057377323,45.2033337118695 +13.5276514759356,7.82007338913904,48.9004316314926 +15.4334816016145,7.16734882872307,-47.17372943463 +15.4206362857277,7.2491775391914,-47.6439623871442 +15.4162105905012,7.29040481130586,-48.3896323022508 +15.4928435273197,7.18527829235818,-46.0149579914276 +15.4121202993777,7.33950131438064,-49.7643554915314 +15.3823713923124,7.33856100676233,-49.5475466194082 +15.3370482588869,7.34346822428932,-48.8000977540572 +15.2831909997105,7.53284647185982,-49.1944800493413 +15.2589145750405,7.57615774263215,-49.6716597031012 +15.2225787380115,7.62974450267148,-49.7235939111369 +15.2146437461202,7.69808648508978,-49.7844632395333 +15.2612220226223,7.80157573672092,-49.5656981192161 +15.2226877699087,7.75862121243542,-49.3256675728094 +13.6181812155321,7.06679350674057,46.0807788108753 +13.8232958849205,7.14920921105985,47.9162256278139 +13.704751549392,7.15303268750209,46.7229466468442 +14.2521363075836,7.69105721526862,48.7882237120672 +14.3246759295244,7.80870621596174,45.0819860110135 +14.2906515162439,7.77860011371004,46.9612461847564 +15.2307576290037,7.91475932298344,-48.3339612133113 +15.279029148859,8.04067791043703,-49.5408899101689 +15.1710225765368,7.83349299891205,-47.3272202091232 +15.1123899804938,7.89020261173438,-46.8856820179725 +15.066783808968,7.88001400842535,-48.3005275842126 +14.9866418029684,7.90596735342202,-49.6575929704812 +6.42779349160562,5.69694915466329,-45.598721302564 +6.43696089643755,5.71303281071897,-45.9607418696278 +6.45029733739957,5.70974219581165,-46.1889310876205 +6.48619729296282,5.69461433815398,-45.7595547923772 +14.5743005029703,7.32893599951801,-49.8830623952359 +7.93692158297719,5.60150632365887,-45.4833508790065 +8.02007329297165,5.6731386182733,-46.5619763602801 +8.13549433735772,5.7091687367647,-46.1793280581711 +14.5003124840244,7.21386654716304,-49.0498126114773 +8.25462688928407,5.70947098699852,-45.6069518603736 +14.457492815016,7.11338957308332,-48.8905337199758 +14.3911551521984,6.72961939748612,45.6397559891275 +14.264874611929,7.12161240694507,46.1815683548418 +14.1909932610432,7.18226944724082,46.138537685836 +14.3311140613275,6.92979083843872,45.5947438861871 +13.9885362818134,6.97355120137387,48.4091150691033 +13.8623723113445,6.86357611275159,49.0434442834958 +13.7832293145088,6.98514710572453,49.8164271441046 +13.7296204321534,7.18533602964244,49.9849820044309 +14.141672563213,7.15815199479461,46.5903428642345 +14.0736185425683,7.09286927723433,48.079657571975 +13.939939851997,6.89163385554256,48.8562288822905 +9.67006903273955,5.2848262093003,-47.1005700895962 +13.6924707634752,7.40188043928574,49.7327411758079 +14.3631640920372,8.10323064455097,-48.437581350052 +14.3629436740521,7.99343721954984,-49.5442326121449 +9.23656453850763,5.73402027226696,-49.9768695220693 +9.06815704829954,5.76734303311613,-47.503944029257 +9.15749182888535,5.79811100562064,-48.5286387993882 +8.9720794768371,5.78426295153645,-46.4485950778583 +8.87889922684798,5.72773803405097,-46.2251738361545 +8.79260669781433,5.63348473145494,-46.684786286679 +8.69467817048594,5.59073264972586,-47.0162762835563 +8.48421256425279,5.58926529316191,-48.9184317061741 +8.57883490492748,5.59420647955155,-47.9314324668487 +14.354269450753,8.13431570970593,-49.3454351709705 +13.7773885059728,7.6693399625631,-49.502940418455 +13.6563165640268,7.64327606774998,-48.8780495792471 +13.6182499378775,7.58249103441032,-49.1375891029284 +14.4034898994432,7.26651906606347,-45.3651444103898 +14.475149094897,7.35762994278577,-49.1068669701244 +14.4216188632167,7.32516520358157,-47.1163686071049 +8.17772286668886,5.34498012530162,45.2552269170337 +8.28878828018546,5.44817375976239,45.1169898592452 +8.08130965675438,5.36138772952114,45.4701503800185 +7.0304453012846,4.90735949170532,45.6076136158198 +6.99527723730349,4.89303407610653,46.4344939279835 +6.96280016751948,4.94627820097485,46.535286721351 +6.94795362035048,5.01151005755899,45.9239083196463 +6.92252217639544,5.09314119989515,45.0755382293023 +7.0001319757123,5.45697073653538,45.1018339666627 +6.98715513784923,5.60866338384351,45.3842559163646 +13.6722391731401,7.92279321124127,-45.7942074642344 +13.6612264513108,7.99655670186279,-46.5815808039085 +13.6881365928244,8.05675631439176,-46.2415865025538 +13.718690458991,8.0286338336278,-45.9676838515181 +13.7657609838727,7.96716431920161,-45.0140971460416 +13.8149645338584,7.89274239367202,-45.0634143436942 +13.9785217713135,7.62509114427509,-45.8787837461098 +14.1295597648793,7.53342706462159,-45.8114863986683 +14.4196390434314,7.49568457134827,-45.7978172382195 +13.8860131418413,7.6472853942709,-45.634474841185 +7.0099375389405,5.74818843632036,46.1596279159055 +7.10312401383995,5.75970712627378,48.9268583635462 +7.62282006546064,6.01411646839361,47.9176975113263 +7.71312943924075,6.2301209981407,46.1871290149808 +7.78346571592574,6.33117904960199,45.4537510209765 +7.85558720769995,6.21638041930751,46.5021536673787 +7.85991080290931,5.98529650990246,47.2367605453526 +7.84652360769809,5.68354454415719,48.1944963966702 +7.80074806080905,5.37352304213385,48.6519258661415 +14.2936643537347,7.49983343507399,-46.0735968116964 +7.07686801276748,5.80639609058468,47.2020736186245 +7.82955700925485,5.20869675298256,49.0078626058484 +7.86018928179959,5.16633063148363,48.7867087763187 +8.29450620277475,5.81589264556774,49.4834018468001 +8.32004663831437,5.87027222035338,48.4167720084978 +8.32737807940228,5.87021102906405,47.7845602116275 +8.36872971309645,5.82957580276901,48.5253126051975 +8.45255078462951,5.80006218321086,49.3449681391564 +7.84534000520022,5.09319928666923,48.8284413892151 +7.87006600910023,5.20730100472914,49.5402729173496 +14.5692789436894,7.46319686716124,-46.3449079533126 +14.7606660078618,7.53956664162376,-46.3892335553172 +15.0813398636513,7.5717791973914,-46.5764529813404 +15.2027311830668,7.57181254374541,-47.204182658949 +14.9392123764829,7.55833418267619,-46.3274942254669 +15.2845746903282,7.47978997648985,-47.448427988422 +15.3892203942655,7.49391402831439,-47.8553492387925 +15.4979780278769,7.35002455972308,-48.7063556019935 +15.5755301528405,7.28306119327727,-49.1614101233796 +15.8099144888797,7.11284836984873,-49.2283372674366 +15.6978165760117,7.18721248575502,-49.3108781362783 +16.0608496533698,7.09396056878694,-48.340190882306 +16.0984135433745,7.13056173211198,-47.0623463925803 +16.0950015597972,7.15352504180092,-45.8863086762916 +15.9194504688793,7.09511385006873,-48.107391826324 +16.1354829352567,7.30468642664359,-46.5737253120929 +16.1221391043696,7.36440123625705,-47.9983922495902 +16.130576799354,7.18839122729573,-45.5481706201468 +16.1018355965311,7.35700320213232,-48.3770549140364 +16.1279250837804,7.43875518262136,-47.71134676921 +16.1056375793388,7.40598890199384,-47.8866416398256 +16.1929920210425,7.54842917063436,-46.4394015174465 +9.20794909116088,5.67496524754328,47.0171888002037 +9.22967081520309,5.54558371085745,48.8778785704786 +9.30645935771309,5.79857634301869,45.4001922812618 +9.24713777378595,5.77256458209624,45.4677122115506 +9.56776152671958,5.83638180642506,46.9021540620395 +9.43363969955965,5.81297716596679,45.8280169610469 +10.0448130122452,5.78127211742042,46.0800891268283 +9.79031816906718,5.80542383828007,46.7588089185006 +10.2656313234057,5.7873888114372,45.2067573813883 +12.4524064737627,5.60363123875415,46.5369531797056 +12.8137704440392,5.5838419578313,48.7801147088638 +12.5756049446645,5.24805381858495,48.9311024731564 +12.257916271037,5.26227555690455,48.1126883451037 +12.3971323251668,5.21794611130691,48.8133035018441 +12.2295128674786,5.2799576922212,47.9555478852096 +11.9669628005919,5.59060904025092,46.9406531366758 +11.78993274956,5.69312397137799,46.7798518932743 +11.649212645396,5.80899961771631,46.3711879853475 +11.4913167652272,5.88420956278104,46.8090203726827 +11.3375100116525,5.88858645441075,46.4913474658525 +11.1659175048685,5.88272000169501,46.6112565605947 +10.9827382646838,5.81299672895076,45.1961873674413 +12.1456162512228,5.41536085015372,47.4015606947366 +15.9279343272318,6.51249670597625,-46.0246521291401 +15.9566247290431,6.67529508600059,-45.6788501190315 +15.8643417517933,6.5748152018152,-46.3121707892368 +15.7799731326339,6.65504101486873,-45.9778003323323 +15.5337510972595,7.44556735534679,-45.1756506407942 +15.5027430545827,7.52262347810694,-46.7052981464066 +15.5452423888117,7.4610407504901,-48.7428738268624 +15.5994989732216,7.42567568291323,-48.7791118062894 +15.6694698411489,7.35098624566917,-48.1278385834617 +15.7763653078456,7.31579841058888,-46.6356699820261 +16.508055710402,7.06637116479523,-45.3641675127729 +16.5706872437542,6.83674773081678,-46.2550684530185 +16.58769592649,6.69585068297172,-47.5378656343025 +16.5785325825449,6.73163076231673,-48.1608009235211 +16.5864253656879,6.78395293149831,-47.4812740445051 +16.5525515832128,6.78810290008759,-45.755362449753 +16.5643719430603,6.79939735126675,-46.8176469072266 +16.684875203909,6.3250985550486,-46.3776945920201 +16.6839289877513,6.15470465614011,-48.4212798151641 +16.7016860187846,6.04536469920981,-49.0679544244618 +16.6841474126653,5.93461274367269,-48.1969744013515 +16.6638807054547,5.89961577789071,-46.03598198335 +14.4115597073604,6.2211954908108,-47.7224120218656 +14.474548873372,6.32501427461574,-45.7679142075564 +14.2772533165485,6.19132895571035,-49.1238799479918 +14.0559968616,6.19724861549262,-48.7312310869659 +14.1673483085592,6.13607158624281,-49.3855962635635 +14.1076764853251,6.21770743555635,-48.5307006559092 +14.0170603939612,6.12736259268354,-49.2744593090659 +14.1871890771755,6.45730566934071,-49.2992247114454 +14.1724091437886,6.66999277887891,-48.3844330432854 +14.1747996060896,6.97222885511844,-48.2503393807098 +14.1591536053347,7.14281422010692,-49.3095832394042 +14.1977684388413,6.21338707555299,-49.1837862793522 +14.1526799746219,7.35559554728818,-49.5185432453492 +14.6108616958082,7.67145988624024,-49.612255522963 +14.7038672749182,7.91975619170088,-48.9145332413938 +14.7450472030259,8.06455752499585,-48.0213837197894 +14.8141887880007,8.1297170468378,-46.9594950448728 +14.8686888003788,8.10568840279328,-46.2287158152937 +15.9634301231123,5.78369259334189,-45.5227984503804 +15.7431725179743,5.96986423094229,-46.1731446338702 +15.6088013289797,6.01041243058677,-46.613607792015 +15.8925900849318,5.94256399250127,-45.858990139393 +15.5311700397564,6.12426776039872,-47.2747935174952 +15.5248516774405,6.17779295874293,-47.9532175088697 +18.2675371912058,8.07742761880008,46.8271094630572 +18.3763883409065,8.16360392514888,48.7067240431779 +18.1308720595981,8.17788442818527,45.2251945219078 +18.4879610377436,8.31528690489019,48.0033453225091 +18.4745735926145,8.23361239482547,48.969775542143 +18.4822082452976,8.31699218800108,46.7585811098954 +14.9864777015309,6.80486902459654,-45.5871680849931 +15.0568982798948,6.65186271767723,-46.7687193863785 +17.356862633228,8.10157115044456,45.9214541825071 +17.2222469284452,8.17631131663835,47.0116666982409 +17.1250779831231,8.21408975300831,46.8514931443706 +17.044631930229,8.22188215791672,46.9444171271069 +16.9705595270918,8.05831353586012,48.5948346532552 +16.9981599848182,8.19129101425482,47.4929767266284 +15.0912634214587,6.60863478025061,-48.2189576663988 +15.0757292302458,6.65587028575723,-47.5552948135869 +15.059034903902,6.69211007683523,-45.9529257942513 +15.0622762342572,6.96197700975274,47.4112327010454 +15.1018214172105,7.04639674367883,47.4100001840814 +15.158967535309,7.11469274700994,48.2905770940236 +15.1836681705845,7.10849469004206,48.9249281016449 +15.1659255394518,7.08097130967575,49.8740339626653 +15.0426894225252,6.92759463376384,48.8201623993536 +15.0991718205086,7.05006407573564,47.7218854042941 +15.294257576242,7.14700805480431,49.2705209306289 +15.274243980278,7.120003502784,48.1858414729521 +15.2516182045657,7.05727400269916,46.9325661040919 +15.2533974953912,6.95411867132476,47.0434990502661 +15.3012065190857,6.85554677975555,45.9359087330896 +15.2726059671688,6.84284654339402,46.4495835411478 +15.751785672635,7.14829213053224,45.1379716885108 +15.8673570926169,7.11086121797411,45.2417015922349 +15.6756313413227,7.23795761722837,45.5237044938816 +15.9753659960249,6.99926726398384,45.1288100111316 +16.0855098855848,7.17297870848345,45.247578379433 +16.0859573895341,7.24502667203781,46.6299907749381 +16.0722927595074,7.32342960771356,47.4510536836939 +15.9991218030009,7.46521311635022,48.1053122356457 +15.9217642319655,7.5665004516911,47.7959486463642 +15.8358411941024,7.72087191293859,47.8982219506119 +15.7628030935246,7.79303530860805,48.3739744870938 +15.6925507466897,7.83591284504188,47.8291173119361 +15.5919254214121,7.89402353758013,46.9207047648929 +14.9046794477744,7.98726519902644,45.0219145424772 +14.8923351833921,7.97275403302963,46.1835244850605 +14.9077002855416,7.94610157507477,46.3339669945375 +14.9003967943411,7.99012825429054,45.872938806276 +14.8883775000985,8.02087032740022,45.4448055427239 +14.6031593209438,7.62360096655995,45.9173951942239 +14.6568646170828,7.57706446673155,45.682977086406 +14.6587173240915,7.57751779180334,45.0807975048398 +14.665574177276,7.64949904374395,45.0723790066374 +14.4940163416409,8.47856166704101,45.7761446469982 +14.5197069133587,8.46980578688775,46.5723696325745 +14.4888908476284,8.54099558370911,45.5648060963998 +14.5481144633918,8.46317978861825,46.1865972832492 +14.5543948324603,8.48404362914903,46.8969707446188 +14.5397129551758,8.47745550345894,45.2553474140907 +14.8043921139361,8.19358419415783,45.6401953019254 +15.7053672161529,5.32681737568608,49.7122687943313 +15.7002939222236,5.28300324001561,47.0600472660371 +15.8387135529413,8.97172781534804,45.0942257806099 +15.8236798236539,9.07764201545979,45.9434240724897 +15.7745724769511,9.1554334188866,46.5967528382531 +15.6935573834646,9.16659480838149,47.2265556403361 +15.63646545902,9.1515991296415,47.1593991766124 +15.5767785082545,9.08593791278559,46.6895683671378 +15.5285350876657,8.99387900007488,45.909158257314 +16.6799824696293,7.31795695874372,46.5542132019223 +16.793216703846,7.27349558773918,47.0596867701903 +16.8948546824717,7.09879780061987,45.5236439959286 +17.4545614380165,6.83586353456522,-47.8970660833964 +17.5877429609983,6.70511845655389,-47.2831900688735 +15.9208670864934,7.00263914327135,49.6993448165142 +17.6762110446632,6.79930231033045,-46.692170913767 +17.9169464842776,6.84297009899333,-45.845473121732 +18.1059054707263,6.9245294023068,-45.8187241412603 +17.9908412896117,6.88817246133599,-45.8781437379814 +18.1615744948025,7.23259801353635,-45.4654673487248 +18.2447320636067,7.68362096138511,-46.9202347794826 +18.2219354739478,7.42214452588,-46.3462736609302 +18.303385177797,8.41207154942101,-47.6396546518188 +18.1486082463133,8.20451795824987,-47.8695710932133 +18.2322429128725,8.35819161536965,-47.3973174654195 +15.4782053220522,8.8797615910296,45.2642747659332 +15.2396706396667,8.42980529559496,45.0716687843168 +15.1263542237448,8.46977470389064,45.578217264268 +15.0053099927607,8.6325825624817,46.3146231015226 +14.890513752224,8.7770961502958,46.3520318054944 +14.8129286148938,8.92929991024589,45.9966220883844 +14.5988952075298,8.98885447830196,45.0118943162471 +18.0831889666581,8.0361048614861,-49.6569198326441 +13.5585021990119,8.51970243320814,45.047439396442 +13.5521275886798,8.55156631209663,45.0273066932368 +12.8773201283525,8.22419723376376,45.2368949855563 +12.975018215844,8.16165599385605,46.7631525851536 +13.0349381280062,8.16375244151037,48.5353929548536 +13.0871798786908,8.12442049932107,49.4287035292731 +13.1280113815553,8.08484851809188,49.2354018511686 +13.1761302029882,8.12620071309374,48.5384750682074 +13.2387394677329,8.23235276389407,49.0506194467312 +13.2868495584107,8.37281712948197,49.6163408487492 +13.9357662227996,8.13198841777193,49.8901093660154 +13.9414726669793,8.09122563157726,49.1351980070904 +13.9335123465539,8.07759726114308,48.1209080519835 +13.9764320392558,8.02988173076187,47.6008987029766 +14.0354614196272,7.9769161372349,46.8141491851158 +14.0991371284405,7.93392416081367,46.3187634357633 +14.1822777264971,7.93887132850923,46.9464533543488 +14.9624899537107,6.87821426407307,45.3424062054387 +15.3359783543874,6.99822489672699,48.8644396438118 +15.4106682578956,7.39014902881505,49.8673922696926 +14.258459465922,7.91134106168756,46.9680021481743 +14.4259107126612,8.11602455614324,49.1149081658563 +14.3692614677163,8.21689708050147,49.8416766821507 +14.3629070346529,8.19555008885089,48.8666118770248 +14.3417682431956,8.05670586330399,48.4515359409907 +14.2444392934459,8.13523977282959,47.5496662472071 +14.2425193355345,8.02621297033757,47.2096954506941 +14.2588875343542,7.9272374652477,46.5133598036419 +14.2726443862538,7.88688404075586,46.2207792351724 +14.2693174033727,7.87223524416553,46.1958107628785 +14.268625948007,7.70003942230425,45.911018912551 +14.260998951555,7.66932091648332,46.2481606502784 +14.2338167085019,7.60878437424149,46.0937960359793 +14.306182913629,8.20584798076655,48.3308009384536 +15.2274209944264,7.73185359759064,45.3877442823563 +15.1495808408603,7.5330720113572,48.0885473499739 +15.1156258321823,7.40816912367623,48.6215470292906 +14.9260749011737,8.03776127552567,49.1450740725862 +14.8910819460178,7.90923885679939,49.0291441571918 +15.030677062521,8.12992473448059,48.3522996084378 +15.1487991907385,8.07110469335821,46.8797808053987 +15.0677963045053,8.09998431962077,48.0603039933215 +15.2391675835776,7.93982447534393,48.9624881119158 +15.2072612943066,8.03936447230744,46.4556498144242 +14.2463307422683,7.72482402576131,47.7717920880739 +14.2256104382575,7.63930581759306,46.3139827911571 +14.2066680568787,7.69542744426268,48.0946248760584 +15.3101684885924,7.77105764861336,46.5837460938575 +15.3002876096284,7.71318728636764,46.6629411197413 +15.252920573704,7.6435526307633,47.0404575134309 +15.2680875666215,7.61095169961614,45.1137914879544 +15.276261831546,7.72063896817573,45.4802813994586 +15.3046645868069,7.85733486883172,45.2704015935487 +15.326397595242,7.97392716637921,46.3479126998171 +15.3439296327746,7.97162448012419,47.4017844123047 +15.3740189880502,7.91436006438647,47.8054334738457 +15.4035484231673,7.84946285020245,46.7552393367353 +15.3990252973199,7.79168091781926,46.1997398524798 +18.3153913748818,7.22505588649888,-45.5984014272223 +18.3849021561204,7.24711651189626,-45.4274361610305 +18.3057501055428,7.10814337095711,-45.8694891939853 +18.349585806216,7.0982529161015,-45.3145604299753 +18.3669266920342,7.57521492571298,-46.6020438553759 +18.3307343192026,7.62755967637043,-49.5945882158524 +18.3756387946519,7.29106200324207,-48.9454458794426 +18.3803951218424,7.02455545584842,-49.7273465706158 +18.3160118528168,7.69243672828469,-48.032489154587 +18.2556417517808,7.96405659173848,-47.7323844903127 +18.1608069456801,8.09683925947996,-47.1314351139877 +18.1206969471342,8.34263740022,-46.3916927168526 +18.3152072637883,7.95105341907284,-45.2734284981327 +18.3778782841846,7.64665943703515,-45.0515406725567 +18.3953806704651,7.50976474212881,-45.6000973488636 +18.485907732541,7.36132480234016,-45.430422920716 +18.4317143356481,7.38649283291799,-45.2157359831946 +18.5751808848066,7.65667279987136,-46.0166947116513 +18.7559534835119,7.92072193866188,-45.5712311894809 +18.6473315462422,7.79926967181848,-46.0513025112036 +18.5150457366427,7.45061266059785,-46.2583722085712 +18.8840590824962,8.01554073997585,-45.4710672572941 +15.8188861858008,7.26604041756453,-49.2296942396927 +15.8270547252194,7.23673674378064,-48.802420695257 +15.8796899740831,7.22483849728262,-48.2713339104841 +15.9709872519615,7.1953682913246,-47.3489389759523 +16.1034986126237,7.25057479268037,-46.7359158485884 +16.1858866507494,7.275429398816,-46.2540713182036 +16.2244984668745,7.37034164329522,-46.6640607863682 +16.2516357250122,7.436871017158,-46.9110325178694 +16.2661842525025,7.46227314544864,-47.612739921899 +16.2704469553202,7.54270612257727,-49.0842842890882 +16.2371635427024,7.51210395454438,-49.6018922528111 +16.2098085249545,7.5294338480068,-49.0289696318504 +15.6715606419302,7.54976394919005,-49.904352482401 +18.8107057816516,7.96683384970667,-46.4642302714304 +18.8514687568145,7.99123249373919,-46.1158892188859 +18.7209200177433,8.02780319556591,-46.4755145581734 +18.6230648717064,8.01238960259331,-45.8578539196323 +17.9226534247,7.75061305034373,-45.4810501618356 +17.8670967741762,7.84328204115433,-45.6339235050436 +17.8104227793892,7.84237221334558,-47.8154429206933 +17.7849481863358,7.77221608144101,-49.515460054723 +16.1809377227455,7.47488601592252,-48.4713436513205 +16.1076408869839,7.17446715471171,-45.6992572160099 +16.1479198859439,7.29567597203215,-47.614950562919 +18.8317887238976,7.06613862660748,-45.4280133340625 +18.6937746923375,6.84945476250246,-45.7041014404684 +18.5769610393822,6.71901613773367,-45.3039047841429 +18.787621020413,6.920136005123,-45.2025639544359 +17.108547310499,7.31456921754786,-45.802012029234 +17.0371163504429,7.25483482242258,-46.4799748767002 +17.0164917363061,7.19081292059934,-46.7066948936597 +17.0030469184351,7.18133435196337,-46.5589680369044 +16.9092071453146,7.15853030368158,-46.2924668845293 +16.8267293356544,7.07636378166788,-45.5884229300633 +16.7103832819301,7.15487927284194,-45.7870086406888 +16.6059378043325,7.26098018333682,-46.2859097083303 +16.4305794423473,7.25871622313818,-47.6649650657611 +16.5286620045219,7.23290344711022,-47.0157125192171 +16.4691436189693,7.2675703496341,-47.8236119241309 +16.4070927204703,7.37518617702751,-48.4337502251131 +16.3639143142754,7.35847805634907,-48.9442783899572 +16.4210018996161,7.30669070649092,-47.4181645419929 +17.0310291510505,7.09768854071323,-49.0341753748518 +17.0867074875194,7.01590785955922,-48.5233655535414 +17.1635110695591,6.96123485720102,-48.4198936857127 +17.2041253333553,6.90546751805659,-48.249500733029 +17.2316057368369,6.85625940930573,-47.3988673390628 +17.2664361875613,6.83545937346602,-45.9111809498536 +17.6930956214735,6.88831798020375,-49.8781784006275 +16.9248256270117,8.4281718760667,49.8983660467192 +16.9962846046637,8.44614029277337,49.8625554076663 +17.0281202547027,8.45217702772667,48.7063295919431 +17.0638919225967,8.51280793098965,48.6138373395939 +17.225685702422,8.57657019065224,49.0543611276456 +17.1500591466631,8.59210385733275,48.770231898623 +17.3229502660655,8.45636811946294,49.0170942456196 +17.2711361508767,8.50763835565437,48.6745804661599 +18.3905881629318,7.24005669774472,-48.3931636327122 +18.2826708900735,7.30875568325726,-46.2235127477154 +18.1754047447198,7.41237272927436,-45.8869931775348 +18.0950301602497,7.49071260190127,-45.6537320598816 +18.0801900758717,7.51624018741897,-45.5672784680314 +18.1228463003646,7.45753433779037,-45.3166187901283 +18.1230488477439,7.45254759513696,-45.9627284504766 +18.1301100980844,7.4566862945732,-46.2786831421369 +18.097147775392,7.50570036647795,-45.5770117936086 +18.0257519627657,7.57637000929256,-45.2586853280834 +17.8550570088379,7.72845072056146,-45.0513354789763 +17.5211741580013,7.60471665669568,-45.4001213835595 +17.6378551111427,7.74161016616903,-45.3944017376949 +17.4079331610889,7.35529451210495,-46.1194809850033 +17.4581522854866,7.4290711841532,-45.2230215091765 +17.2443760734328,7.31317832732946,-46.5425924653125 +17.3161200932604,7.29990916235512,-46.2803839195697 +17.1393988654639,7.49847189342151,-48.0657435387522 +17.2095484076132,7.38726611843289,-47.0272916154308 +16.9735406315401,7.76738600453947,-49.9720622712789 +17.0593503940606,7.66048116499417,-49.2365553439782 +15.4101950353009,7.38772769003815,-48.4158644876428 +15.3721483132311,7.58953390377294,-49.7880315934734 +16.9162201596736,7.85599650236038,-48.6325855351656 +16.8035300460341,8.00934985201188,-48.351675797157 +16.8628202958393,7.92239933569576,-48.270296307108 +16.7094810676499,8.0495561003648,-47.9720960625631 +16.7614492319407,8.0367963400753,-48.0390004764822 +16.7220251322505,7.89843147184383,-46.7326285913292 +16.6944730656684,7.9940695464385,-47.6814136159528 +16.7844082829272,7.61696947992499,-47.0816213373348 +16.7769023483036,7.82328097342456,-47.3103882824536 +17.0023374861636,7.41440808777731,-48.6681519415404 +16.8337658011487,7.51934172864441,-47.7936212197021 +15.3391171432839,7.77149491769744,-49.0707598115457 +15.1622639753147,7.94198076403336,-47.1025608120858 +15.2520160587625,7.8025761963028,-47.8907898694656 +14.9014178364001,8.35776905121698,-45.7534349010819 +14.8689937617111,8.25136882074496,-47.2656385558947 +14.8106155280302,8.12636391227397,-47.8048188219482 +14.7823695558038,8.09340244132631,-48.9228270650381 +14.8824833335371,7.85008527188004,-47.8949878883275 +15.0552105290224,7.76690639087619,-47.0045804758008 +15.3087858012552,7.79574927015043,-45.4363935986587 +14.9622547575587,7.75642984438513,-47.4853733781498 +15.1643303152756,7.78911743416506,-46.2628990080309 +16.1344291931295,8.18133437743495,-45.0579110339219 +16.552890896077,7.71103094944292,-45.8182057995724 +16.5570997835842,7.92371794186361,-45.0063302183383 +16.4862014697534,7.60055795140936,-48.0723418119675 +16.5396689090412,7.68133140907221,-46.4695637357428 +16.518190589661,7.67688492445405,-47.7746683277929 +16.4567412869243,7.62004409173029,-47.2986101588382 +16.4723967378883,7.64497081159179,-47.1039276746588 +16.6519241608653,7.69941176445078,-46.3738224492439 +16.7662191379423,7.55739166882174,-47.1448606511581 +16.5385570680103,7.67034526415012,-46.3349353214059 +16.78423632065,7.5741351054312,-46.7645367338686 +16.7083960098144,7.55188705822324,-46.7528607087077 +16.8255789387418,7.65316923886055,-46.7151672312459 +16.8409468850627,7.73117050450643,-46.1146419163721 +16.8416528978201,7.68897576438266,-46.5480360138828 +16.8455412695088,7.93522763009861,-46.1978788562994 +16.8431445101594,7.89169488056599,-45.458156596314 +16.814144858757,7.83384044270477,-49.8192463413114 +16.8564309689712,7.89506022875115,-48.1272738150841 +16.8817185895276,7.95286941277586,-46.7823208298542 +16.2071148619517,8.58624677379521,-49.4144846285436 +16.2865320776364,7.20939591549718,-49.6352007891197 +16.3374612622959,7.20614744854194,-45.2993173035156 +16.3190356094865,7.17182770551911,-48.0344827365834 +15.5405069010747,7.42827967817532,46.2633118560367 +15.5413391806555,7.63305857542273,47.1492609131517 +16.3254951376795,7.45239147689802,-48.6906391943121 +16.3413370988933,7.51461913775861,-46.9763308067287 +16.235185795932,7.34373758944174,-48.3639498917463 +15.4648708579314,7.76378333465652,47.1588427046468 +15.3574378678133,7.95584974063361,48.7421353606244 +15.2469387162118,8.09214173005832,49.5865133722521 +15.149326330225,8.22379339433656,48.1124442583623 +14.9825413089282,8.27457557944565,47.4116666706025 +14.9621150288227,8.46508573236147,45.1110900244451 +14.9408181402659,8.35911982914432,46.711294636588 +14.9249117574999,8.41137037980846,45.7413056289836 +15.0592123320336,8.52679613844729,46.0766188496493 +15.0095214780452,8.56327073485351,45.3590769373088 +15.0474790746,8.26947566027059,47.2990400627935 +16.1274129471426,6.68567490838114,-49.5833756814898 +16.2183045039322,6.73311269646257,-49.5582520968642 +16.2740349766206,6.83582376740521,-49.1421038766523 +16.3437315077043,6.94676331106403,-49.6380780935039 +16.451887545198,7.05874511183826,-49.6276185830694 +16.8397258646749,6.79363265315561,-49.0755120336087 +16.970597446313,6.78370568349269,-47.9648870684091 +16.9934131526999,6.90372856849632,-48.4441015445915 +14.8354326188955,8.04634629493404,48.7864571487811 +15.2362255048476,8.06615318053456,49.4417151113162 +14.8052748776346,8.23120409262705,46.5171598198606 +15.4356888785678,7.99029725393607,48.9100954497712 +15.3211850499256,8.04227041798244,49.3358440870632 +15.6707097333328,7.7294966050593,49.4324851523618 +15.57594659595,7.8630707234111,48.7864408539732 +16.6000596346858,7.89895321871187,-45.0352320098866 +16.6266166971834,7.69444923819063,-48.6764807412067 +17.0268718276766,7.05829819394642,-48.528427976828 +17.0296773953064,7.09019991991357,-46.7976429158758 +16.8993176487046,7.19637404325421,-45.040220556715 +16.968245156449,7.21284791647904,-46.215882059548 +15.4421264988888,8.04462572469714,49.9990947282358 +16.5864752013884,7.61607113653932,-49.7045864430062 +16.2528001985317,7.81839156079708,-49.2447409467594 +15.9232858561405,7.86111161857621,-49.1964012214819 +16.1709866962194,7.85238285805264,-48.1046702625187 +16.0448516574139,7.90214092490922,-47.6774608876645 +15.7807542429385,7.70894743046846,-49.1280068655293 +15.8306588013683,7.78338068395199,-48.9022631620768 +16.7382043832294,8.55431265004833,49.5092856278179 +16.7165782424078,8.56579949837251,49.628360566362 +15.7140373956552,7.58281454676201,-48.6174646646443 +16.4714280814298,8.16358254769184,-45.3819153243783 +16.492498320237,8.01829845317859,-46.468537959329 +15.9891131619493,8.79651628081086,49.8064324561805 +15.9470889676342,8.65896388903095,47.8931928064098 +15.9788183829195,8.73187375506066,48.967745702995 +15.9336554892707,8.56383014872036,46.6639650578769 +15.9197430378515,8.43741729248299,45.5596690359527 +15.9384638438856,8.06883234638937,45.6538867208303 +15.9619140920419,7.96071966657395,46.150491716412 +15.8952189196395,8.07128384512218,46.3789857400128 +15.8714525600131,8.19487888197208,45.7598542084748 +15.8496263829719,8.36490266059148,45.3497914898723 +15.8178251641236,8.57567250439952,45.6605258247933 +15.8860195773218,9.21162393695329,45.7220921430377 +15.9785534878277,9.14528235322549,46.0718409013595 +17.3578189086562,7.63268528283246,-45.6751520700476 +17.389966783006,7.64651549528988,-47.3563246137245 +17.4475012182266,7.74374942590158,-48.7954179610697 +17.5657621118877,7.81568123943169,-47.2289095900525 +17.6716349359755,7.8920810843437,-46.2462876343977 +17.6998713082904,8.01285371508893,-49.2271343358089 +16.1409656410663,8.99797300926147,45.7865829400229 +16.3084121076584,8.83765566206146,45.2230020131534 +17.6913742338247,7.62640688314179,-48.0439642850281 +17.7182372275992,7.75838153398581,-48.6029883067715 +17.6446095050627,7.46019760825684,-47.1055331187817 +18.0934918357016,7.57350997808265,46.207411176196 +18.3469321691592,7.50494677032945,46.7206746077926 +19.5165481422935,8.03215217141518,48.1871701469711 +19.562006224552,7.85454247441571,47.6324013874636 +19.5661176860369,7.74643624752779,45.8336336157711 +16.8157639337414,8.86154920831795,46.0056720367843 +16.8310596734247,8.78658022256904,47.0946239158466 +16.8132263552143,8.78031466585267,47.9066255411181 +19.6661603340369,8.16375506180215,45.562172111877 +19.6523829573662,8.22556454530723,46.5582641050465 +19.6937555227798,8.26655485912083,48.0310617123363 +19.7141724195762,8.12170882692434,48.4384629172571 +19.660272661201,8.05875970113081,48.4436013596131 +19.5369284258055,7.9880246681936,47.0862092247027 +19.3800546107199,7.97961933388218,45.528623307355 +16.7896123246467,8.69605333356749,49.7673334781751 +16.7241113319821,7.71695792555456,49.5108476064607 +16.7195343609937,7.70989227025985,48.5244360237981 +16.8117723297246,8.73101751396529,48.7374434564127 +16.7217458091899,7.75530944300232,48.0094467412447 +16.6938807595634,8.31579626268645,47.4799875914624 +16.683899876581,8.6076192379799,46.8183847499525 +16.7065996447009,8.00663323561115,47.2282406294679 +16.6828896219781,8.87873181984156,45.1018648509367 +18.3388555013618,7.60637466787975,46.2565430379024 +18.0967586019387,7.50947638323455,47.6639410584003 +18.2302950792325,7.57313556689517,47.3899710969525 +17.9533152973484,7.54000085987699,48.4494979755148 +17.7863034602478,7.55245522985267,49.3179109937257 +16.6837541154405,8.97172847892347,45.298571741889 +16.637634568775,9.13564421324685,45.6821802330237 +16.8629637749275,7.94998347857852,48.4558230798435 +16.7293086787139,7.99166312286106,46.3104615517466 +14.6490249813568,9.24957584245796,46.9941864151304 +14.8252799349219,9.190551869716,45.3641747759145 +14.3941368424593,9.27667761093568,48.7826672312613 +14.4938790794281,9.2919436479302,48.1257134948823 +14.2663446799018,9.19582960924975,49.085313439928 +14.0725294451884,8.98351149368239,48.214168616138 +14.061274478086,8.87106370148115,48.7261386567541 +14.1399826205391,9.14442824686704,48.5072208298681 +13.8684711744282,8.77750192117251,49.0466687489823 +13.895169431667,8.8233807897604,45.5204271043337 +17.4912653582095,8.05680320671844,48.4470257441281 +18.7263298516388,7.63425411929305,-45.7660292964796 +18.5664848542888,7.74463607168201,-47.2068597328954 +18.3713755977666,7.94692087219313,-49.2237141388314 +17.5876067996729,8.83538246828905,-49.5847197067971 +17.4185697303627,9.03776420717661,-49.444523389536 +14.3977162045701,7.03506888358259,-45.007128941232 +14.4086513309167,7.0443358903131,-46.6685065319621 +14.4807125658912,7.19207759615378,-48.3253006584766 +14.5191524791263,7.35033387755305,-49.3688711269733 +17.2254390377493,9.04320137355714,-49.8259804403788 +15.3978447619018,8.60840399105311,-49.9599315752544 +15.5039505832955,8.43528915970009,-48.6027165869742 +15.473121611581,8.49075608763825,-48.5439586528115 +15.5626917091539,8.22704608606738,-48.3586843373901 +15.6153219587558,8.10123474644085,-48.4574073517869 +15.5825291414488,7.91973235263837,-45.7324147745279 +15.5584363073159,7.96842635849254,-45.1285980220532 +15.6077249076409,7.94502230247198,-46.6848106808049 +15.5827254517744,8.29491976547634,-46.1445180605193 +15.5568124601014,8.2339475022374,-47.2715645962888 +15.5540574322152,8.26573107019509,-45.4755175144383 +15.6133083023433,8.20973492982433,-46.6260969066821 +15.6717359804823,8.29950532082531,-45.8028132893488 +15.7387101877789,8.1303482007321,-46.3802303694677 +15.7917828869496,7.85202179992843,-46.8514452125439 +15.699932814595,8.32929839017698,-46.3740005455894 +15.7745999871497,7.70242950153386,-48.0438208664549 +15.7843236330664,7.83257996214167,-49.3312769186698 +15.7981689708119,7.7523711207195,-47.1850630038873 +15.7285512461513,8.04253962326759,-49.5982911486489 +15.7598947887179,7.75094761542059,-49.0209202617816 +15.7175958323699,8.08652186780298,-49.7802896730929 +15.7381491865495,8.00454820838225,-49.5651041458829 +15.7398104234721,8.00899573525585,-49.8976704348935 +15.7152598886719,8.08843129458412,-49.0170237542241 +15.7115959615568,8.04743729614217,-49.3561721386937 +15.7729821588728,8.25575049937118,-49.8803733547513 +15.7758134710767,8.12579430913953,-48.893136320363 +15.7626393984873,8.17875476646296,-49.8882496602735 +15.7751304799271,8.15563438879944,-49.9394368226275 +15.77741114472,8.12643455327202,-49.7808961892672 +15.9478069904639,8.01476068678996,-49.613296159217 +16.0258937959301,7.9281959062429,-48.8831475482231 +15.9761600916907,7.98856966398124,-48.8524317798197 +16.1941005755963,7.86216535919887,-47.9028465849315 +16.0959365876352,7.86340659393172,-48.3763663742212 +16.2645513581123,7.81420538687822,-48.666536288708 +16.3169493660823,7.86071782007114,-49.7226345713304 +18.158209500281,7.95302076762849,-47.5434819468474 +18.2541105204286,8.10213068586559,-45.8725243820399 +18.4104646476496,8.06414670090293,-46.9767905496051 +18.3905850270763,7.94315792538367,-49.6038903917266 +18.4148600121005,8.00136549782401,-48.7983436710217 +18.4411201111157,8.01650824493474,-48.8548871125687 +18.3559725069738,7.5348422158334,-49.8937638651359 +18.2119946251546,7.3019117172831,-49.2422347189276 +18.1704338791333,7.27431037447152,-49.4188699704918 +18.0945577339778,7.25900914545719,-49.3938808374244 +18.020250092957,7.29227852954253,-49.3027607701704 +17.9085682144965,7.40890293324831,-49.1573206439809 +17.9619717925373,7.29670796192427,-49.0485484588282 +17.699492172717,7.3652193094054,-49.641300915298 +17.7903946075096,7.4297869959336,-48.0502256657738 +17.8480558506272,7.51481241508064,-48.1125985608883 +17.9197798632561,7.93862039341474,-49.2616101429469 +17.9004170929949,7.77533400228475,-48.2111062804708 +17.8775697591277,7.68458176000052,-48.3977251691574 +15.6207839564238,7.8077420708022,-45.5208030714198 +15.8476579349305,7.89461976931968,-45.8853405001968 +15.7220978173029,7.85487527587605,-45.8178943046755 +15.9303535189245,7.83854650122193,-46.6897002202171 +16.0083163065449,7.68936554575597,-48.0277187427927 +16.1016776379985,7.57380144991013,-48.351825905532 +16.3302372197504,7.33111458183035,-47.5618483284835 +16.1950691707479,7.46330735314569,-48.4848774015691 +16.4436361786065,7.282983780677,-46.2183620959669 +16.7599920995374,7.59248280483935,-45.6355438726554 +16.8115648156447,7.68710524642702,-46.297387321788 +16.8594111928544,7.84816855071748,-47.6573062708097 +17.0852514521186,7.99063949365715,-49.1505550042093 +17.0896979860313,7.89825031177804,-49.7828471629182 +16.9086849916238,7.82951613467713,-46.8208050929765 +16.9958142072663,7.83154639501526,-48.0543939075838 +16.8171279552713,7.9067855588551,-45.0980332397181 +19.7622302087585,8.05388385868944,-49.7147904156107 +19.9185469859327,8.11499648816964,-49.2226625638478 +20.0707545649993,8.26063050860231,-49.8558948501928 +20.6967290446139,8.068600558675,-47.301006789405 +20.5790342495286,8.09171325012825,-48.9446362780933 +20.8938399825234,8.02914921294641,-46.9917846507906 +21.0134396035767,8.05736048367946,-47.7749247545232 +21.2709443350375,8.01529201277968,-48.1289365480548 +21.230928986198,8.04535247366506,-48.7087839620238 +21.1477266761636,8.04075190486653,-47.5942603678348 +21.0225814843994,8.78161118064589,-48.9561127403244 +20.8067405997348,8.60718350148481,-47.4053311407209 +20.6927628498668,8.48167264741371,-46.1919746151877 +21.3195531328597,8.12911487769571,-49.0085374808202 +15.8188082512971,7.68128880362572,-45.2662872490586 +15.313392574594,7.82816904657674,-46.2946318384276 +15.4646252271592,7.96047507200503,-47.2009237567087 +15.5638453312809,8.06768621771264,-47.2831258884877 +15.6573761273821,8.17054111858033,-48.4143376152652 +20.0021399804144,8.46994467862158,-46.1116365931096 +15.7340666402012,8.12358401920008,-48.5958111699942 +15.9366567551436,8.0774518635543,-46.8695558578144 +16.1229375731138,7.78619183416391,-47.125238834978 +16.2335662400437,7.62049513087322,-46.9621909363744 +16.3729700113033,7.6071152741704,-47.2516029845825 +15.8132916090929,8.05881262206798,-47.4412970251366 +16.0248737432238,7.91490106630797,-47.0327242317147 +17.0239732197152,8.00252670451668,-45.6199701003686 +19.8649322466625,8.54138568572856,-47.881853766125 +19.9702387313509,8.53683374516978,-47.5127728269572 +18.7302844269778,8.45753650390382,-46.4600566060091 +18.0574197504401,8.55131877831878,49.363108661371 +18.1220831551566,8.58081920491483,49.4531397095625 +18.1680074258604,8.60393531910829,49.5305854228693 +18.2166022017115,8.64042906176328,49.7566153258148 +18.5618030066959,7.92369787601502,49.1229204261874 +18.5507253167863,7.92804854663944,47.2292753680128 +18.5178519382388,8.11537059697028,45.7668237825932 +18.4701504741418,8.2650835018242,45.5302179086045 +18.3597655939478,8.72682835433335,45.8919467360264 +18.4239711994539,8.52567143808295,45.5046403190279 +18.278373980948,8.94501297741666,47.4238613450329 +18.1985313580031,8.9864822273001,49.1205468118405 +16.826355134242,8.11950155626665,-45.0938325439718 +16.8329743227297,8.14705871805176,-45.265231187127 +16.8473570539742,8.14114538511336,-45.1177889304971 +16.9028921609492,8.28663703970443,-45.3294806004422 +16.9396048842043,8.05419934836279,-45.077595776551 +16.8901388346815,7.89898339825955,-45.5675163595244 +16.8755115659329,7.84661295241516,-46.1047379766792 +16.8427594113137,7.83104332872231,-45.8858155328255 +16.6644102457988,7.97932146095864,-45.7842161507905 +16.7809492513209,7.84592643927028,-45.4929012980968 +16.8300945780675,7.8338572474658,-46.6200423372678 +16.4001746026534,9.30767323838788,48.41619267362 +16.2771050336934,9.09702108600264,45.8742598460428 +15.9347917721433,8.70752095927844,48.045811919223 +15.9800434438829,8.72709318490178,46.0011221509431 +16.4555632478758,8.11279840772269,-47.169013165364 +16.5996127117567,8.07495445841007,-46.1031825216392 +16.2868844561443,7.93484035585446,-45.8811446341357 +16.3418119020806,8.02306088765721,-46.6945449854535 +15.8539043336724,8.71032233499475,48.9020394296748 +15.7413985915219,8.72768262386225,49.8999675730934 +14.4942464803419,8.11165439807351,49.820141798149 +14.4654376962015,7.98854315096411,48.6969135160076 +14.4122642911783,8.00163170626345,49.4409964887711 +14.3893445336183,7.95930221635075,48.9828303555721 +14.3451979466369,7.89003525997164,48.8149507599514 +16.234161690803,8.56657089492356,49.8406713030764 +16.0877060767698,8.64882891910438,49.5607319298927 +16.1792031348647,8.64187558051399,49.5931487408658 +15.0802236386827,8.79979116331499,49.025159357112 +15.0759808076107,8.75916818069146,48.0278006005629 +14.2044386310062,7.9174274709005,49.818653900023 +14.2589723475362,7.97732835754354,49.8990412499032 +14.380205310458,8.72843305478484,49.1160706933875 +14.447977677159,8.82244302104906,47.6973322156136 +15.0721485531118,8.77852817413883,47.0033518183578 +14.5497462546511,8.86297065028076,46.1065811714538 +15.0799561889878,8.75663244758819,47.7056396037914 +15.1157997107902,8.83267632030908,46.4574271714048 +15.1118508225993,8.82457735924539,46.2906433235113 +15.0963467593204,8.93812666581424,45.0582208889629 +15.0514782620691,9.18621006971328,45.2638507357707 +15.0199524802681,9.35385961494456,45.4283057397602 +14.982392017119,9.52929811524193,45.2072776402021 +14.9588918683214,9.60883184725713,46.3452804072825 +14.9697941503023,9.64118554760166,47.5095934341851 +15.0382149685673,9.62548264576942,48.9282006578845 +15.3231492476551,9.34119714612051,49.2522004220336 +15.3728860910149,9.34004509846049,47.5005037234124 +15.3621341035927,9.31793296183464,45.4122214698346 +15.0806122519983,8.88266097867909,45.8934222417784 +15.0502861287654,8.92767866598194,45.7219386552097 +15.0863352014011,8.86148061354437,45.6281502601032 +14.9805562883067,8.63954611883279,45.6711807383772 +15.1013668136924,8.25164160826346,47.3700848603912 +15.0434787125615,8.43677091670933,46.7986407888288 +15.1294879395573,8.15206190150229,47.1622660812644 +15.1338846115592,8.10913619869104,45.9058794758222 +15.1379344773127,8.12443105029611,46.2882063372315 +15.1051528234741,8.09997341632771,47.2983250033878 +15.1218488045629,8.0667441224341,46.829074050409 +15.0806740125681,8.03333628101893,48.7464995188768 +15.1041243951127,8.05427380654462,46.9737947557176 +15.0224191812299,7.93002261725835,49.411667049698 +14.713470335174,9.40859976738363,45.4950441695633 +14.6563337375135,9.21893138360941,49.3041721365516 +14.663306793679,9.34397399354577,46.7974457054634 +14.9648067357051,7.88496071262799,49.3716816455607 +14.8205487977544,7.90513282347628,45.0224957124466 +14.8999092519299,7.85081319162542,47.7815976791118 +14.8566773806354,7.85128306270167,46.2016045312369 +14.7833812623049,7.95337545658248,46.7255519368609 +14.8380750459457,7.89555268914363,47.4499839920059 +14.9141773577629,7.90143960880432,47.2621513442704 +15.0059111364451,7.95827700592033,47.9352391700148 +15.0897683769541,8.03544080363152,47.6646855160512 +14.7497158308163,8.04483088936795,46.3082524366907 +14.7264662524397,8.0659743238093,45.201311474973 +15.1609029477875,8.15655888857952,47.7602514919868 +15.2410033345371,8.27425828402077,48.4334917811515 +15.4728687520958,8.36069354129851,49.9571765823424 +15.6366521659876,8.26909175067233,48.789558798525 +15.7842405823471,8.2092457302591,49.245786198292 +15.345405736788,8.36147845102139,48.5510190185715 +16.0200956056583,8.00441058276874,49.0377649333661 +15.9208434028908,8.11174281892742,49.6312807986542 +16.1244903435663,7.97949465834306,49.0194182975289 +16.2012919669417,7.92702779692682,48.5794444263582 +16.1738547584432,7.93138028992843,48.1058687999969 +16.1816393984168,7.77702931915633,47.2392819955919 +16.197084395192,7.89203551051863,48.8720938987175 +16.1952330080449,7.75089351920321,45.9349163980289 +13.1947852763636,8.9228486209658,49.4691335008768 +13.278543722824,8.824809869474,48.2323194554746 +13.3087983030947,8.73693758495824,47.9556452010477 +13.3077705701434,8.69015995004966,47.2712630205384 +13.345607928391,8.64446775260076,49.0079315533808 +13.2867563795104,8.68361919096883,47.7423169965602 +13.4333480286216,8.61916877244375,49.5520371136187 +13.8678502988445,8.28894476177271,49.2365531463284 +13.9377639576661,8.40954454291592,48.8211142230979 +16.1318933099978,7.44326789804785,45.0733768411359 +16.0170746544735,7.16240792479945,47.5799311899989 +16.0839838391988,7.35912393906795,47.2117774470262 +15.8726674133276,7.00910437470333,48.5642577815577 +15.954618341937,7.01859764919919,48.2620176026873 +15.7376333918734,7.15075503955034,47.3377335941679 +15.7874326949962,7.0491667375131,47.3811647506606 +15.6740795755248,7.33141196213809,47.26204020511 +15.6720806081511,7.38036490946882,47.4353608055858 +15.71528470554,7.284877999579,47.3496582914156 +15.7496740008699,7.17166090445096,47.2778883472699 +15.7618757869914,7.08364356436481,46.7097029398893 +15.6947847740694,7.26597204382153,47.0907164410924 +13.7991261862919,6.93407457008467,-47.6317094198103 +15.7407869180453,7.06581369339593,46.2502085653267 +15.6832648148873,7.33687098399485,46.32459642834 +15.6481741042115,7.46824478246159,45.8233733001719 +15.6294105989689,7.5842765023151,45.697392179944 +15.5758913205494,7.64202656935085,46.0726733525771 +15.5305316882053,7.59898566158783,46.7973764064548 +15.5142727880542,7.56079302522824,46.9357909406331 +15.7072927341984,7.19784124283756,46.7060582868056 +15.509291835455,7.5473429677178,46.8329257945786 +15.4861831016887,7.53097693892973,46.9392135830244 +15.5071158231258,7.50358945745162,45.7132373148396 +15.3871278696255,7.58863717944044,46.7320819707259 +15.305961467376,7.61218062227371,45.2854452592677 +15.1949871957049,7.6353853313361,45.0527504696209 +14.3463378453914,6.99327649194699,-48.530225396299 +14.4397541779931,7.09467150362576,-47.3444566383964 +15.4378940284483,7.5619138558303,47.3933885053089 +14.8026760216761,7.40977399687025,-45.14904603527 +15.1217765020761,7.8234882833947,-45.4542718687505 +15.1206447852975,7.75036523317553,-46.2731795346345 +15.0964797149701,7.70522419248961,-46.5819362964899 +15.0502290567739,7.70232417455239,-46.6637189994989 +15.0340245153296,7.77774135292427,-47.1705557916999 +15.0598535159525,7.96152936085025,-47.8571416096762 +15.0322022974788,8.02532719832696,-48.6113967789842 +14.4010250930778,6.68855900397712,-47.623865375099 +14.3289255126612,6.63252646234689,-46.7218013379679 +14.2384354441295,6.6051563756327,-45.3917501857555 +15.0930339004665,7.68644868808657,45.6099617374863 +14.8883170522736,7.77330116054596,48.3437704321724 +14.8015823246054,7.69464593775075,48.6585783112585 +14.6970025388711,7.583648882297,48.8696396516077 +14.9839573051136,7.77899874954182,47.5453775702403 +14.5912586595444,7.52825253114703,49.0993660092913 +14.5216898852338,7.39706444856145,47.3139614971471 +14.3668681919371,7.17372275546439,45.2799498486303 +14.4857492424601,7.33381323194628,46.0147245884433 +14.4175381234009,7.24336904187409,45.2795646917827 +14.0947217629441,7.11470354787514,47.9310085812538 +14.0120030680883,7.09456514643313,49.0682512069539 +14.1700951090734,7.12522073886475,46.8328120308861 +14.2312341146075,7.18980699320591,46.0747071094702 +15.1578245249505,8.36031148302409,46.9042374064506 +15.1602631023485,8.58320631109862,47.4494616730874 +13.6421115605921,6.90803887310659,-46.1561425632653 +13.6795832091964,6.85804007212133,-47.5117982855984 +13.6862475687774,6.77758196506665,-48.4272057973417 +13.8095909904601,6.6713871917837,-48.782839838995 +13.7412556535706,6.67524600615859,-48.7009692630353 +13.8907532071958,6.60676011212195,-47.6408351385193 +14.0213801708581,6.60027756258853,-47.6905110560644 +14.110123726026,6.65111052140849,-47.2988102472559 +14.0626630432144,6.62577256255871,-47.4142404649043 +13.9785220037324,6.62062228272032,-47.6757858565303 +14.1963396839532,6.69331169410596,-47.2965786596848 +14.2941917447001,6.83664430164036,-47.8689378972193 +14.1993192969347,7.64061464112392,-46.930412078703 +14.0072519374091,7.67904364511599,-49.7037743563556 +14.1141361889576,7.60392156750879,-48.2392707406877 +13.8969326147663,7.63677986305174,-49.9363838359667 +13.1804995646711,7.4412310257541,-49.5533829349311 +13.1709734891137,7.40923109039145,-48.4388207263115 +13.9192035849027,7.81838560917618,49.9604616190715 +14.0060781609344,7.9087194236458,47.0790630106416 +14.0802239043089,7.95291353849162,45.988878540191 +14.1645551017746,7.83446368056697,45.361558050531 +13.1778099818447,7.31804672582204,-46.6673268976272 +13.2331943428026,7.38150950381032,-45.6523844243509 +13.1709638781206,7.3369790641939,-47.623624540014 +13.3067441684909,7.75968336215613,-47.2704960806327 +13.3436354236504,7.96989867371261,-46.7768153030485 +15.1629279341489,8.75910504033368,48.4577737342423 +15.1680904443389,8.62308721502098,49.1568246690169 +15.1706093312904,8.45313699170249,49.6210135124149 +13.2785848564859,7.57397838367212,-46.4633523097042 +15.165488625102,8.74162752700771,47.8432400665116 +15.1859296448527,7.69503723487184,45.8234344024389 +15.1901433694825,7.54560909774959,45.6564062935647 +15.1923298335688,7.4336413447065,49.1711118985839 +15.1879749459705,7.60251760387532,46.3649700493125 +15.1942832374967,7.35241222393812,48.668168528 +15.1751028549318,8.13125852025724,49.1085430548728 +15.1729192482584,8.26144310794411,45.9331181069594 +13.3958357021797,8.18935733634417,-46.3015108427757 +13.3834354350892,8.07516008304939,-46.1759696025617 +13.3777264511445,8.02099797464425,-47.3065376621591 +13.385441457507,8.16807392521334,-46.0453206383112 +13.4053599617119,8.05615191764166,-47.5521735079596 +13.3239457326093,7.95785675823227,-47.4148448835358 +13.2892129705039,7.92718979701223,-48.5165513670943 +14.7827660380451,8.24062878811324,45.2301510659475 +14.9016762026275,8.21476063871464,46.1701893589914 +14.9746097992145,8.16871532741805,46.1754518644166 +15.0083317825095,8.20717192552814,46.2475870363515 +15.0193316195209,8.19062596383561,47.5755257360574 +15.0380859296494,8.20266111131713,49.2868903065395 +13.2940713702407,7.68480551348488,-47.1860777663485 +13.2102149050008,8.04534724285791,-45.5527427109403 +13.2179073351315,7.84136977218453,-45.7913795838319 +13.1989269072677,7.94449210147225,-45.411380508783 +13.2178059852521,7.68102505857067,-46.087875000408 +13.2212172279104,7.41080757651611,-46.1198258875975 +13.2027118438837,7.26247748612689,-45.3646643868784 +13.6055214892668,7.42962897460738,-45.6652381963734 +13.6629401873698,7.58476817188537,-48.9105503430626 +14.0731921750088,7.80683344532965,-48.932486297585 +15.1962752633659,7.2662031617503,48.0271434213434 +15.1983047678549,7.14963013024164,46.8053462598647 +15.2002046020928,7.1158362520612,45.0714664733558 +15.2042217878046,7.41513794312856,45.2635859880704 +15.2062751128261,7.64759708530483,46.974307228378 +15.2107156993768,7.85300974245612,49.8654994206694 +15.2151182278746,7.67385546507666,49.9271810710275 +15.2084121872776,7.81143318754958,48.6669092420477 +15.2171114963092,7.49515807266408,49.9436961067696 +14.8381658418597,8.62030982356696,49.682857668969 +14.7722356075711,8.70284740668521,49.0823122392322 +14.7181761938778,8.69086796588009,48.5413274177878 +14.6248306984168,8.68012317339661,48.2564203479501 +14.5070816552752,8.62990541400023,47.7937085741207 +14.4301298747109,8.54389431206329,46.6695889636825 +14.3462584950957,8.37920690114909,45.980556740102 +14.370189761158,8.48183134500647,46.7129704587605 +14.2634696022514,8.3338237696647,47.075823726437 +14.3288316529635,8.31412279315438,45.8090728260535 +14.2810066911618,8.34238474249526,46.8602847363426 +14.122995008813,7.73334599861868,-47.83819620132 +15.2362881276865,7.84335554954838,47.3341149901496 +15.240615508019,7.94166679118688,46.5840188919939 +15.234232782994,7.72743286636207,49.0709309754003 +15.2427971645347,7.91724133018076,46.3516224959025 +15.2384241558864,7.8943199412771,46.582407519666 +15.2470335530122,7.73480392117006,46.2664059346112 +15.2448446932619,7.87997427156689,46.6000725057785 +15.2489965684066,7.56440221616995,45.5892788165063 +15.0199601469033,7.93845521237497,-49.4498450659034 +15.2619605475152,7.38140231153216,45.7346853490324 +15.2638828007181,7.38346231944268,46.17346671409 +15.2657445184029,7.34144234007303,46.5576299210142 +15.267554125801,7.2937789494608,47.8677896132633 +15.2692553570855,7.24511523414196,48.1202730096829 +15.2708655299728,7.21235893708218,48.0332364779714 +15.2726318397065,7.23995776856484,48.6932552568358 +15.6467647806321,7.6478539109525,-45.2717316927803 +15.5054743024042,7.53809732989945,-45.5759097200927 +15.9027067579579,7.97722457351502,-46.656561864937 +15.7788989327919,7.77079432510935,-45.0777511342957 +15.8899378694916,8.01754434690716,-46.1539832969497 +15.9713803690782,7.99702142025828,-45.6536342896009 +15.8988184992681,8.08540163792296,-45.5729968962129 +14.2390833754288,8.3357794953311,48.3806245541811 +14.151014305342,8.19830510624347,48.8146036739923 +14.1298504788181,7.96512205784368,47.1894814206449 +14.121583887318,7.92256796617428,45.9966019413042 +14.1986332872546,8.25910519137683,48.2874754284036 +14.1416896398116,8.08713403699341,47.7832017712395 +14.1643458721045,7.9789915522847,45.2034020928725 +14.1488484329577,8.00634509003537,46.1942951062308 +14.118524326289,7.86197976484707,46.2720524855733 +14.0959622569943,7.8383286497407,47.4970929495168 +14.0459625220511,7.8882872059961,48.0512635378349 +15.2907867213658,7.95171051084304,49.8565068548097 +15.2941416508493,8.17212699643202,49.3821312806967 +13.9792283052005,8.0164504193084,48.9579681368834 +13.9469957564243,8.21821527865307,49.1771706250874 +13.9849693683009,8.4047393069147,48.0069326765588 +13.9900695934658,8.54952342684605,47.5711285265239 +15.292410576365,8.0462206397753,49.2872223389197 +13.9985940258316,8.75787596114063,46.0254659707815 +13.9920245962607,8.6911640175163,46.0126211313552 +14.0130251870922,8.81864681034478,45.8843191562551 +14.0040266990661,8.82140639690344,46.3376013195991 +14.1094836618156,8.76747095313794,46.294665634379 +14.044631263732,8.81554128996806,45.7968494074835 +14.1845433421,8.67603396824687,46.0034781579809 +15.3147765572536,7.55067959345806,48.8791649208061 +15.3151172458536,7.44643552670883,46.5073428287004 +15.3148668415163,7.48297302338999,48.1170251944661 +15.3151850883479,7.42970399842706,45.311435897968 +15.3149355082582,7.51724928780012,46.5110329337576 +15.3151388939436,7.49537908848281,47.0814837293814 +15.3145664513229,7.63814810951811,46.6434939344894 +15.3137488090506,7.78148954509182,45.6948752440247 +15.3141812610089,7.69579156959167,45.8359414323463 +15.3133758764219,7.8362339380114,45.2314247186965 +15.3093434045913,7.79114248836853,45.1508349503292 +14.0132559925131,8.18517362842503,46.5335214260897 +13.9575078968253,8.34841368366497,48.2372641428322 +13.9570626829356,8.46060626962185,48.3913347988019 +13.9733349269804,8.59917264167506,48.4720271408807 +14.0253385755169,8.75133929025308,49.0835264129061 +14.1057145122495,8.63265916399149,47.9251251746074 +14.0962863352796,8.51103503233142,47.0340211946317 +13.9831333535014,8.67895195498494,48.6390730376689 +14.0029183207072,8.26289921961152,46.0127222641295 +14.0420690856092,8.35531081495083,46.2892242630865 +14.0620622589994,8.74224833281573,49.2669085310931 +15.2983988481417,7.31954800127354,45.1163844490441 +15.2950092404796,7.30223743458886,46.1527419784323 +15.2911441519222,7.33056645508512,48.8295169732658 +15.2818146473914,7.44333101309776,48.8299076176545 +15.2763628178021,7.55324427610065,49.6766386284366 +15.2700176871834,7.65589999239159,49.6335010361515 +16.6623119325678,7.45968519919573,-45.6805085699036 +16.7284902648431,7.480109853638,-47.1568047878323 +16.0160341278737,8.6470588675368,49.2683177653224 +16.0880958769375,8.59286737940619,48.3678892320819 +16.1718844920372,8.49612640416917,48.1983114916362 +16.26316866921,8.38329407449349,47.5775837769136 +16.3479793050726,8.27113256166517,46.8901074108812 +16.8544356292206,7.45597646489022,-48.1048918480542 +17.1720552825901,7.62021565649102,-49.9711251427809 +17.1370887398406,7.53171244717532,-49.8324729759638 +16.2682665621239,7.8296495758379,-49.112673924526 +16.3424579686604,8.06980054978731,-47.6544919276022 +16.224311410712,7.5973954537423,-49.218845491927 +16.9981562285553,7.48899566486445,-48.8870517713921 +16.4182823478398,8.21888678607481,47.0744495807 +16.4914393066805,8.25032842831772,47.5635402302295 +16.5230477669221,8.30811632887975,47.8561010330928 +16.4993947012411,8.33374090595469,47.1825813265524 +16.3519229839247,8.37160071770002,46.419537061041 +16.4293402698099,8.44574008980266,47.6583944443527 +16.3139629959167,8.3356814095465,45.9248735784735 +16.396616878303,8.2247989602172,47.2540268164857 +15.6532932901932,8.16774557958614,46.0193124809321 +16.0268812368672,7.4521606471628,-49.1334004699268 +15.8351729711389,7.55362971855052,-48.4530804914249 +16.1479927295281,7.50772180519961,-48.2238714233872 +15.6602685082617,7.54571424699513,-48.9692567901562 +14.901545990254,7.62282868384571,45.079139943081 +14.9929018354023,7.63661061456146,47.1116062116711 +15.1341941366173,7.74475096119781,49.1209446564625 +15.069618616808,7.67728744802864,49.1323052415044 +16.4510670617417,6.90021923309986,47.9027978934699 +16.4414060836493,6.87495482888812,48.721619560892 +16.349871514853,7.09333673889876,49.1716531235283 +16.3870931894007,6.99669154675352,45.5949540179013 +16.406591566858,6.93167072323434,49.2236125762683 +16.3185997102205,7.3000155652037,49.8676469646048 +16.2674960360541,7.49716860582702,47.8954846181407 +16.2967107448807,7.38608820223778,49.4283279043708 +16.2434977674544,7.58857879308499,48.1204513646354 +16.1459396260204,7.51355155208779,47.6686129408856 +16.0822164796332,7.45037862249145,47.9013475776886 +16.2139876884201,7.57905720811569,47.9507613432891 +16.2251274774049,7.60009038137622,47.4324395621672 +15.2467321133016,7.8655342919939,47.7327597420521 +15.4661259328754,8.20269147685966,45.0369541562916 +15.3721243853424,8.04422898952934,46.7804455668478 +15.4657645944537,8.1448656314497,45.2498604234743 +15.285551174043,8.06285853673182,47.6610395599071 +15.0766731496966,7.98744284971739,48.2844358548593 +14.8190462399438,7.89556290181522,48.2969266372546 +14.5832438461853,7.81765358179458,48.0221542806878 +14.3335891318088,7.85866231816687,47.1999158983147 +14.1521641166295,7.91772773628401,47.2948363518888 +14.0020098969873,7.9908949961272,47.9077655221194 +13.888240694151,7.95986351208157,48.4829144623258 +13.4687752394684,7.87811069777354,49.9697830525764 +13.7208515145216,7.95681724286558,49.6034558424461 +15.636401236699,7.81633577439532,49.7226027579453 +15.7303631768035,7.77547085933146,49.6058272814605 +15.879547541791,7.75368286128455,48.944096000437 +15.884133469919,7.7524254723634,49.6466709250411 +15.8028968927764,7.74466023004205,49.2219336537142 +15.6421449802658,7.85854973569107,49.8126540482608 +15.5022632397675,7.95843031260589,48.2021003643614 +15.3667921223148,8.08782462474239,47.8332749330912 +15.6416386037732,7.84263664780816,49.5806027131115 +15.2566834336214,8.21983424946693,47.2762496954782 +15.1847303646659,8.26471450460637,45.6509528051407 +15.5916865590509,7.80740012184813,48.7468956979823 +13.0985259142479,7.51127538213923,49.1002079369795 +13.0122478905687,7.5737235918299,49.4274605516492 +12.9400732018811,7.63761374895735,49.6231563005535 +12.9468288295629,7.98962817169014,48.7056806733211 +13.0311012586536,8.12181190464034,48.4811679930552 +12.9425568605218,7.71811064842003,49.1243061039716 +13.3771687647217,8.09760487961685,47.8438330209195 +13.5851242013752,8.13839832924147,48.2336518338582 +13.8050272293707,8.14966896417358,48.1347458776483 +13.9919344707783,8.16396002830349,47.3790471547719 +12.9238575005743,7.84379247897805,49.0552718052149 +13.182353135069,8.1426173540197,48.5856166254577 +14.1740215648688,8.20609236934025,46.5197374142771 +14.9649241582134,8.52433635695605,45.1945732448349 +15.1081584137463,8.5092960781778,48.9985009768367 +15.0423996165401,8.5750997761927,47.0221884352774 +13.6359441687776,8.00346833024552,45.082485810272 +13.5861008642222,7.7757386203796,47.6869933396554 +13.4707859491911,7.48969790624115,49.5328602778359 +13.6371619110077,7.20079088165135,-49.2197056827266 +13.598844718532,7.16565661336939,-48.5801049920484 +13.5874223001899,7.30952389173264,-49.0736919147306 +13.5862751195734,7.37531848617777,-48.4066045638676 +13.6055549895959,7.49143845896,-48.5406552782377 +13.6197416922053,7.60947208919063,-48.262304254291 +13.6203544532559,7.63053780116692,-47.1803518465391 +13.6028032480727,7.6296257655491,-45.0152814302256 +13.2503509305593,7.36634610857237,-45.3489457015033 +13.1779717954158,7.37718118645831,-45.7961691399992 +13.0802892635158,7.46389173241198,-45.6148091221221 +12.9886370605064,7.57743636535438,-45.5095612202119 +12.92611410509,7.56865452733896,-46.1978010601335 +13.3294471769376,7.33866648334205,-45.0532428657312 +12.9116688178091,7.56794371834033,-48.866845444286 +15.8054696836623,8.24047844453651,-48.9461087377991 +15.8382445019513,8.23797803956506,-47.5795655739437 +15.8908059904756,8.10998399181849,-48.5492356414539 +15.9777450238281,8.08927769795006,-48.8906000553707 +16.0497850135255,8.0641410053361,-48.046592822382 +16.3750024523517,8.18781137293341,-47.1704119773895 +16.5786670350152,8.62363962468931,-48.2414758242064 +16.6664953333865,8.82563426579947,-47.2735047862098 +16.7754598602592,9.00504035094414,-48.2785927218638 +16.8202110329682,9.10955513674922,-49.8861804639483 +16.5245421720708,8.14642222959227,-49.2444467069651 +16.4554551581728,8.27549722323902,-48.1405340077974 +16.4246215800549,8.28797413681492,-46.9475984597856 +16.2631987031273,8.26886575599416,-45.040277522165 +16.3839276564548,8.37140335839236,-46.2680073618239 +16.1350992037639,8.22616508068764,-45.5688297494156 +16.0517932034561,8.18643829280801,-45.4693143608593 +15.9058622911345,8.16331986615047,-45.7090568456189 +15.6665104984113,8.23416978075656,-46.7342844404366 +15.755052366126,8.20118154252061,-45.9913443141272 +15.642179760192,8.14153758054701,-47.163575845625 +15.547670319469,8.1356953103135,-48.6138208872862 +15.5868220921058,8.15235815322585,-48.0784996499311 +15.5089380239034,8.23096891992556,-48.4789792635737 +15.4026958638879,8.45912739397121,-48.7771699780681 +15.4697335348076,8.33944142506401,-48.1764615383297 +15.3454349779373,8.62348141090843,-48.9217474482445 +15.173098079471,8.74343022380546,-49.4373361412648 +15.2580376704873,8.73278135658623,-48.8830199648951 +15.0952164708949,8.65236544528425,-49.900190334697 +14.9143370514389,8.52928248447306,-49.2515892175161 +14.855314854021,8.49937435681665,-47.8383317023948 +14.8205216742156,8.51437283444858,-47.4643498878118 +14.8505388745074,8.48802005433987,-46.2879127861105 +15.1973795115513,7.52619734190877,-45.8840599485887 +15.1993773745483,7.53561180899168,-45.7764983692056 +14.8464196545782,8.25478298785383,-47.7323842755182 +15.6450155208186,7.36968084080053,-49.553327565775 +15.7498873569995,7.26399711101758,-47.3759415669315 +15.8473641613211,7.22924891928329,-45.9023172212743 +16.3428986839728,7.68249608340871,-45.6657548780466 +16.0739513614925,7.3223052860609,-45.1813748714355 +16.5540701849038,7.81464774750724,-46.653689131466 +16.6978942448722,7.95752025258733,-47.2313605779275 +16.7469229572751,8.08399140406314,-45.6674628014633 +16.7570247022676,7.95558647133371,-46.9156763809196 +16.7339224647731,8.12089325051205,-45.0716371130906 +16.9393867472374,8.44306178096806,-45.0818444989652 +17.0726234148651,8.50424223341907,-45.5097663851294 +17.4726021497997,8.65561643970732,-46.9645446223346 +17.3503469905469,8.59593151790885,-46.8679589008538 +17.2174384644722,8.52554410692785,-46.360104733922 +17.5645149123203,8.73669318478069,-46.8572296979353 +17.5923679733707,8.87471240436113,-45.5690844623055 +17.6331348952222,9.05257118234162,-45.3474025990885 +17.7015435107615,8.84620501250184,-45.8291681812108 +17.6659891070387,9.03450877475233,-45.3207728271268 +17.9458486336704,8.11511778722661,-47.2675271103145 +17.8858502771347,8.44206657653928,-46.5506014720366 +18.0105272161444,7.87268309353336,-48.4467319224625 +18.1000437875833,7.66520219290722,-48.747779483852 +17.7992938967585,8.70687550835246,-45.6657394300398 +18.179005268358,7.63755892206015,-48.1108112445768 +18.2897701637665,7.67838643265032,-48.5140001124779 +18.581927053551,7.93445090352351,-49.9375526496442 +18.4494609716295,7.75950692774452,-49.0627752357374 +18.6631063947802,8.03831592375293,-47.6770876967879 +12.8430405772608,6.53840829482284,-46.8336709138344 +12.77600195136,6.82474988949267,-49.8740494171918 +12.8093021137962,6.75569308828248,-48.0063145618556 +16.7535504369018,7.93577486250801,47.0359254011802 +16.93475514335,7.98320286251571,46.9493740389565 +17.1481612884575,8.03215331823928,48.54529893892 +17.3716657119069,7.9638357014097,49.257018728908 +17.5768316208455,8.04491061334272,49.8451876791435 +12.5239921271995,7.35691833645219,-49.3170088924419 +17.9086940823871,8.09794254948483,48.8210979945474 +17.9878554205743,8.12154816544477,48.1852047685638 +18.1365497117551,8.04934458928066,49.2711685445124 +19.6752274940079,8.32586740908398,49.9324073758661 +19.7311639319958,8.24156651397203,49.4652074107431 +19.7482416402713,8.22181286911391,48.1115928893762 +19.638928096394,8.20160262436843,48.421276182117 +19.6250137094186,8.16342789618709,48.7717325858692 +19.6672228043293,8.04354606001337,48.0681958422999 +19.7127913584932,8.2057999469883,47.7725519067745 +19.6870027577577,7.88315668328969,45.4058021546508 +20.1622023270013,8.23985231882444,46.9900430013765 +20.1521740965062,8.14891854100279,48.4831053000307 +20.1309826841427,8.14304571054081,48.8878576367408 +20.0882667785386,8.31512896938196,49.7274349250286 +19.995871118877,8.38205968815303,49.4078056504083 +19.9976042108664,8.3412953815885,48.7837296956566 +20.3158269103709,8.39860399942241,49.4652927042453 +20.475710031001,8.42532555958715,49.549744231434 +20.7163594516746,8.38660791377877,49.2224780175008 +20.9029536194778,8.48964735557138,48.594668983913 +21.0168205486835,8.41246286499787,47.5510334588687 +20.1129978681634,8.31339496326665,49.1658922001538 +14.8101126127061,8.30259609182603,-48.0635262174459 +21.0536477766049,8.5033287927929,45.9255785754837 +21.0157760657733,8.50832754896458,46.1611554879912 +20.9835407915623,8.39532531482943,45.2675611006334 +21.0074422015488,8.28356727898542,45.7827581393861 +21.0063047728878,8.28787413479224,45.8495408884905 +20.9299709555343,8.27704633103183,45.2297038276092 +21.3014873615456,8.67817484256536,45.3008784795667 +21.3580038440654,8.59437339052875,48.8387082020953 +21.3246904615612,8.62451600731565,47.3457680558055 +21.1915269972772,8.51205739818337,49.8044834514591 +21.2250251311588,8.54602938021954,48.9976740347694 +21.244570899519,8.55210690671785,46.825764073858 +20.6020640430164,9.17838004694472,45.6554866051926 +20.5521644971082,9.1581122500159,47.4516676883228 +14.7477979415489,8.0857615278272,-48.9129752809392 +14.6876029288537,8.06940150146288,-49.8015907697119 +14.2439615408465,8.21453821008394,-49.736872423517 +14.3101435870811,8.11834471616745,-49.4223141081724 +14.1769649401193,8.35046829584007,-49.8054073266725 +20.4538193645587,9.13104668483711,48.8093053304906 +20.0469575214374,7.90984088338409,47.5923256338195 +20.1185165246796,7.79390519676831,49.6182717771899 +20.0402022235781,8.00723211926777,46.5256404888181 +20.0823956672384,8.10484570831448,45.9291612475579 +20.0385839528174,8.19838607274976,46.0198012139772 +19.9616510918776,8.30256179010678,46.2207709942724 +19.9957189099819,8.49347241359766,46.2443425463289 +19.9937127614552,8.6702843568844,47.6468977824189 +20.0018623452669,8.83387771048659,49.3288636569837 +19.718127058042,9.53198292671186,48.0606399047479 +19.7843861598304,9.70517169943522,49.4312079619447 +19.6530575536226,9.26951080544424,47.4113295205913 +19.5770746463967,9.04590038894204,47.7560493290727 +19.4611253535119,8.87063606049701,48.857828398813 +12.8389211561328,7.91908365244584,-47.95733931199 +12.7581703428791,7.92631393043226,-46.9375746999358 +12.9194851410041,8.00076373220553,-49.3911861320136 +19.2816441529888,9.10794404975079,48.8717133756044 +19.2316730159401,8.82465390871028,47.3988258762695 +19.2166149819686,8.60990681168126,46.1882060920217 +19.2031335931196,8.46467779492764,45.4730381090581 +12.6765488578617,7.89703758086102,-46.1496381055728 +12.5116055286831,7.9853075652018,-48.1232630830448 +12.5942162422195,7.95936190986835,-47.4087901945571 +18.9878445113168,8.93943212415434,45.414154076348 +18.9404017305657,8.90522764139693,46.8063498379249 +18.8499172846886,8.83141153129848,49.5330809479417 +14.5200008591956,7.18586930743219,46.7146460329958 +14.5057459875141,7.14965633908884,47.4943721823228 +14.4915508443467,7.06453838878546,47.7762271634147 +14.4774312545912,6.88560694549947,47.1505608005002 +14.4633355439828,6.63454326134582,45.8240033434556 +19.8893674544746,9.39357783332411,49.0554314599412 +19.9681699654525,9.24743917682929,49.5163563557905 +19.8132006684676,9.06217693578155,49.0584476080865 +19.9079172159335,9.16159915115127,49.4559962570061 +19.729648801366,8.89918114372101,47.8840329695337 +19.6120824992033,8.69441342671955,47.424636904566 +19.4925044295374,8.62478849960908,46.5996123370101 +19.3306165911472,8.53930932838261,46.2265664328779 +14.3229110050039,7.21000537633964,45.2508992914903 +14.309063871967,7.21865213744815,46.1404117798285 +19.1951837530607,8.34567674804184,45.0419362130278 +14.3214864931764,7.25492711401315,46.5487885751446 +14.3333178418472,7.23219002705381,45.7159633086442 +14.3446049105556,7.15322881490745,45.6475952703549 +14.3553809507857,7.08072488810203,45.8615859054219 +19.0737493504779,8.51289654923208,45.9927133592003 +14.3656606896556,6.95838776850817,45.9465288168694 +18.9780440695881,8.59875459103297,46.4921605474527 +14.3755069206057,6.96673888582086,45.7164969768672 +18.8786307406718,8.89017618340372,48.0806777686691 +14.3849566395454,7.04586614908798,45.7993750399481 +14.3939774981628,7.15056405056797,46.5575623606418 +14.4026050424847,7.31033975491896,47.2608737689722 +14.4108594005186,7.45291740038326,47.5791730084397 +14.4187909420868,7.47949251138957,47.7321155708593 +14.4264163785764,7.5262074064064,46.9898413391901 +14.433700510854,7.51005403475117,46.5649194105065 +14.4539607228391,7.61842176727334,45.318349290259 +14.4661341607471,7.84151831441788,46.1105427229473 +14.4719332681633,7.89832125889008,46.9644962046759 +14.4775299037238,7.95401688241419,48.3931941734439 +14.4828925065506,7.98843788918005,48.9809581535925 +14.4880484937526,7.99492786315326,48.3482342785473 +14.4407439097315,7.53670234777186,45.6833352449968 +14.4601803390283,7.76310541916822,45.7211530469225 +18.3900813064882,9.26594254982881,46.750957960928 +18.3551723991313,8.96412124862785,49.2176160671524 +18.3514280787247,8.99085356846067,49.3002577615789 +12.6446619304498,7.95231455371373,45.3714058110031 +12.6708032096402,7.92298717617945,46.6621819505515 +18.2737438632901,8.8857063539057,49.6358128344725 +12.2061688531538,7.84589778652666,45.1106258609384 +12.2438655624037,7.79006759903863,46.4868664477478 +12.2618614021244,7.80535866134016,47.4909185878375 +12.2792759661549,7.83981203774608,48.873219833605 +12.2253055842626,7.81014730518401,46.0462578150789 +17.5070694615065,8.801903549265,49.5998673233496 +17.3804359151303,8.92267687863876,49.9820254756665 +17.8553966849385,8.75761370372096,49.9535689579492 +17.8827656632068,8.81638101947457,49.6056632907059 +17.9522770318772,8.82137882338975,49.9751457378992 +18.169622473642,9.08240030392725,49.9563735123239 +18.8389520469892,9.27505265086544,49.518914219416 +18.9156957547256,9.32720687873996,49.2620115457197 +18.9707230037396,9.5599436686416,48.6265335813708 +19.1190356313511,9.71631724319658,49.4546009743565 +19.0378424667086,9.6674903295744,48.6678422287777 +19.216759176402,9.74931942795174,49.3692756140276 +18.6840911099952,9.28075271520204,49.6907015227603 +17.5276414669077,8.70113644021436,49.6297295074696 +17.5356261225983,8.76098320239912,49.2642637147107 +17.8128303601195,8.63089970968641,49.909516439389 +17.9320256824174,8.76766063781079,48.956545660871 +12.2961962246791,7.77502668674682,48.8541290838861 +12.3126195473469,7.80552029572561,49.6504556893789 +12.4764754226642,7.7950454741943,49.9804876591238 +12.4878902606523,7.75487613435611,49.969619742836 +18.0430787157665,8.83642215479917,48.4445745104001 +12.4989291325517,7.6227944516019,49.4378295423585 +11.9733581645605,7.48013730654471,47.4653202989911 +11.9938512897312,7.52187906252098,48.1049927386332 +11.9529961884671,7.51174479222467,47.8301859696982 +11.9327607870829,7.57626910268441,47.8904712503569 +11.9125981674983,7.59003828559287,48.592800165221 +11.892696069493,7.61741792531395,49.6834745075233 +11.8139634187003,7.4053363957398,49.6286079536001 +11.794599950817,7.40931102293187,49.7350142014549 +11.7752849474698,7.36136799554313,49.7046070341124 +11.7560787366716,7.28816987171014,48.9922157248175 +11.7370583179356,7.26235700640689,49.3466918133424 +18.1206916383104,8.94282216903818,48.4707031996834 +18.1639548533039,9.06494854827449,47.6204810223767 +18.3196127951608,9.15714696784409,47.5851725696416 +18.1901424224493,9.13240482250661,47.1411195945401 +18.6142980055842,9.1971468579762,45.9268545934698 +18.5167402567609,9.19070280371093,47.0041385791208 +18.6313232835098,9.22605908891418,46.0269747628921 +18.6658386407392,9.2589063423712,47.7636348323925 +18.6560229284294,9.29784375700146,49.2455665188804 +11.7181568429318,7.24415796123748,49.7365449251549 +11.6807546188102,7.13878156882517,47.383858470131 +11.66222543614,7.12225476164951,46.2806787049529 +11.6437585027917,7.20972822291241,45.6422558111468 +11.6993984608105,7.18147585172405,48.8737371309806 +11.6254059573001,7.28316090570874,45.4339449057683 +11.5891515057601,7.5808176427558,47.5476602272462 +11.607210584474,7.43189273810192,46.0316761988996 +11.6496556032983,7.25114587604674,49.9520356197034 +17.9805234386991,8.60726848477074,49.8061928499592 +17.9098635595853,8.98387826196521,48.3657531928943 +11.6643196312345,7.27461988732301,49.2782256676298 +11.6926150319335,7.46920125781012,49.4711711976884 +11.6786168761643,7.37634330024607,48.8733307868497 +18.0743090147823,8.93227324547733,46.7943689643456 +17.9688456850192,8.98280352250211,45.9387838626264 +17.928246859304,8.97425009191882,47.1612654819817 +18.3323521945441,8.65404462999669,48.0217271105914 +18.1553142779269,8.83412914674798,46.4160256836492 +18.2244583914927,8.78531549950807,46.941163972575 +18.4088163070787,8.58675408490968,48.1882260346583 +18.453274327434,8.55428176545027,47.8146377389305 +18.4739390668833,8.66614170708766,46.6976079629428 +18.5188435720603,8.7342350189801,46.5363972961325 +18.5470586015953,8.86922049281903,45.8060657717074 +18.687256551502,9.0842646468379,46.3480797813785 +18.768913616732,9.24954716826048,46.4507628039988 +18.8132171028697,9.43393912043776,47.7785871487433 +18.6113342061613,8.98144197451033,46.2650555984673 +18.8182064576133,9.54316630728751,48.8923915337052 +18.9018664164493,9.73860156492893,49.6146455243828 +11.6188219359632,6.39096539054656,-45.5661397587524 +11.6278905216303,6.40880156240313,-46.7097071201676 +11.6003099064923,6.60756391350081,-46.9768083681109 +11.6096251946405,6.56995780387277,-46.5898041062185 +11.6005053185558,6.85571998219476,-47.9261308378367 +11.5908686359494,6.73131749179481,-47.2446983892393 +11.6197074186676,6.9274089992668,-48.7134357111066 +11.6101166796217,6.87282304466437,-47.9274426533965 +11.6292736107676,6.90778513492891,-49.8264605451428 +19.8119510943175,6.83934979457433,-47.0318071130059 +19.6805691150015,7.13752647306153,-48.5256702772109 +11.8577563556959,6.9559647555293,-48.9060386772874 +11.8493022061864,6.93432143427261,-48.6772630193925 +11.8407542420042,6.96437041528448,-49.4788028688406 +17.1878762783866,8.11910782397831,-49.4233499087011 +17.0482488857952,7.95565008518748,-48.1462261860436 +17.0731688609375,7.9613534099095,-48.3072904983216 +17.1314637816996,8.04590119440907,-48.5064497578767 +17.0922551930071,8.02406155702085,-48.0367551749993 +17.1229619612608,7.99650109651715,-47.9650536519416 +17.3070388274821,8.07602779468572,-47.0773574985676 +17.1646377398402,8.00936715150902,-47.6983196220385 +17.605924951595,8.36316053664515,-46.549336016147 +17.4909355716812,8.15769533116678,-46.4292118937894 +17.7697600772447,8.52761720132813,-46.4726516056127 +18.1010249039236,9.01346211216525,-47.7448230442358 +17.9373943597637,8.75784075504924,-45.9071188837032 +18.2801966910766,9.08066673864879,-49.3628975113891 +18.4336358710161,9.24419198310117,-49.5277222175636 +18.6031440690505,9.25820083402992,-48.001194624712 +18.8088382875368,9.20093627629459,-49.0709174525981 +19.0321617896017,9.21834444229148,-49.6011050783232 +19.4986281733598,9.0605751079456,-49.2512256273675 +19.3532595781352,8.76203664865476,-45.5567982598833 +19.4197504518267,8.81441677627743,-46.6004189339278 +19.2715187051284,8.8974419472879,-45.2199659957126 +19.1727044161253,9.12282640952795,-47.9202491784886 +19.4335400394125,8.85847835147763,-46.6761027510823 +19.1090558549612,9.21207662426682,-49.1752311042355 +19.2405924417112,9.09276057564667,-46.3385536068668 +11.8022147832692,7.47997969906477,-48.6518850580851 +11.8129167757634,7.27297695921705,-47.790595391914 +11.8236011708748,7.09717135575353,-47.7489517095019 +11.8343271503958,6.98000179311286,-47.8378953854753 +11.8450517113,6.96203990963096,-49.2927253875991 +11.9203133447019,7.27164859566546,-49.7861781670325 +11.9310996665616,7.21224172424205,-49.5949954008029 +12.0179597822366,7.03022344297514,-48.9991327221805 +12.0288377916779,6.96657398266842,-47.8215459893479 +12.0506402084927,6.84936066811642,-45.9681590045797 +12.0615404096053,6.82565726329382,-46.032624228729 +12.0724585432891,6.84883057201808,-46.6998019649423 +12.0834938442034,6.99277182899336,-48.8726047057979 +12.0397635514166,6.87949711931319,-46.3080240818443 +12.2061775014516,7.95345871947107,-49.5795984300598 +12.2174337979869,7.94468408837564,-48.94440298507 +12.2287077937276,7.86128798477201,-47.0852308099506 +12.2400101582091,7.83076167259684,-45.5207894221291 +17.5587587110517,7.0985652940946,48.083636304075 +17.4490433897512,7.45107992424162,46.8955425365744 +17.380063025901,7.57100867719799,46.8047425147916 +17.3475399993126,7.65431059860937,46.2374249954564 +12.379071793689,6.71629929926677,-45.3469995615565 +12.3912332125091,6.75154238833011,-46.1660394068212 +12.4159865031559,6.83352895484059,-47.1204125408656 +12.4283356942374,6.92924644224792,-47.0065470848763 +12.4035860338,6.75090361028921,-47.0320752886579 +17.3141216848059,7.83988624583212,45.9022102357424 +17.3465210366007,8.10826487157903,45.3242078466733 +17.3177762083246,8.01517895299428,45.373209188665 +17.3358800836033,8.29272304421398,45.5070529673445 +17.3771320909145,8.45216199396984,46.5960805238818 +17.3563194862787,8.41118064417832,45.8959808369363 +17.3976658016255,8.50935887964196,47.2087111156937 +11.2688196284426,7.00132218197846,-45.4596058687435 +17.8826338612135,8.83832990671687,49.9008442420146 +11.9554116217865,7.89639301519286,-47.376351701392 +11.9593722403408,7.9677893702568,-45.481688437626 +11.9517421064591,7.81397432986563,-49.5159850540695 +11.9173063202406,7.16874248574158,-49.9287917966567 +10.5129586824527,7.10185912842712,49.7698810246285 +10.2465116854665,6.94013741935472,46.9594103205609 +10.1091511743049,6.92350361319088,46.5553261054595 +10.3960938427341,7.00424826552587,47.9570625083801 +9.88097783928955,7.03725372970541,47.9659148520218 +9.82397322842143,7.02955069185849,48.6820893355992 +9.97675032291593,6.9800193914623,46.8069425286861 +11.8718780941307,7.13356300996451,-48.82434185709 +11.8656405981273,7.03011611185613,-47.6768911120175 +11.858852195309,6.93779709733054,-47.7735846610248 +11.8516320538267,6.85805753567009,-47.3817997978961 +11.8439608349051,6.90417409388653,-47.0882609199409 +11.8357571484615,6.97310597597634,-47.3832939596072 +11.8169936300904,7.18339170736486,-46.6484499967625 +11.8062994402371,7.27080525939608,-45.4981587475217 +11.8266408434038,7.06311801602121,-47.3829885684389 +11.5073891778076,7.93820847222161,-47.8691283289878 +11.4010881347279,7.8977673177907,-45.6500738754042 +11.5583363715415,7.82215826272722,-48.8784233677879 +15.4169400261404,8.31641477886063,-49.580868039842 +15.4137019031898,8.43745897753724,-49.2665556042987 +15.4259898354949,8.34266557360151,-49.0970508827859 +15.5268621964554,8.31170269847223,-48.0773991143179 +15.6017543561542,8.28536454927292,-47.1285766532912 +15.6729861300734,8.12213111349554,-46.3086647476241 +15.7556555131962,8.82725614846139,-45.8343042879843 +15.7113022983478,8.51683039386467,-46.5198576374081 +15.7302916196367,8.68603638239298,-46.4975876208439 +15.6905443664581,8.31622625004135,-47.2359662385644 +15.6430038317173,8.23119016129038,-47.5369482341525 +10.3938767734006,7.45128331411889,48.2605128652077 +10.3939315277227,7.78255609723893,46.9556182467996 +10.4058294222924,7.61146658074149,47.0776884053462 +10.3590718277078,7.93423748499709,47.2034783438325 +10.3994395619924,7.87250742802121,47.9098279616272 +10.4030500949752,8.00982850767553,47.7204809028015 +10.405408170826,7.99236761270657,47.5478776523874 +10.3189290286428,7.98878034484691,46.9630481934019 +10.2963478629855,8.07621803319918,45.197454509551 +10.3473787833451,8.00218650948909,47.442564649164 +10.2773678374091,8.08014230473535,47.1581269015842 +15.5368037599873,8.13468344828379,-46.0703568322088 +15.6236715383527,8.15138449850437,-48.391715257906 +15.5844170559068,8.12795405895291,-46.3316119020103 +15.520934185085,8.11887057923057,-48.1604433658111 +15.5496016171679,8.12670862455242,-49.068122542369 +15.5919676346729,8.19781421795075,-49.8241391441324 +12.4048981161531,6.91284062355519,49.2609067546873 +12.3544891470001,6.76471578247834,48.5142312072429 +12.2814021206853,6.78607044907646,48.9554332045699 +12.2157612414426,6.8214001094418,49.0707988288256 +12.1689346714605,6.79804192533926,48.7540239365506 +12.12806106807,6.70206276502183,47.6275012453967 +12.1056957522723,6.4773366962303,45.6743084338162 +17.0656280791261,8.72606494216087,-45.2537517493314 +16.8986144764123,8.20440739726236,-49.4502943913171 +16.6591304126557,8.23383613115121,-49.0761281841031 +16.4275732502809,8.81895897176948,-48.4320848195332 +16.5765863284868,8.43135387350461,-49.3201294975312 +16.3617898291334,8.96480373871534,-47.6783330705411 +16.4938085784157,8.64862163060261,-49.0797836579123 +16.2842260809327,9.12795481745972,-46.4632850228656 +11.7136932733018,7.31189380891671,46.3575115592042 +11.5990160478247,7.23174976869848,48.4928513307981 +11.6745529264924,7.27576157362216,47.8844444392911 +11.5326991078661,7.2869052514785,47.8195055054332 +11.5513175167793,7.26209473816896,47.9680535494456 +12.5902313032353,7.58453653685624,45.23799343998 +12.9487330237239,7.77442536711446,46.7942696838171 +13.2641079783704,7.67045911624643,48.7661099623264 +13.0791202803585,7.80809278389989,47.7000881864001 +13.3151505419495,7.58931275164004,49.1128687281044 +12.8042325561748,7.69910061913752,46.5003568323522 +13.1966486018444,7.78409484385862,48.5340898061831 +16.2243990650045,9.26408386754373,-45.488822708988 +13.4152547240376,7.50027367833736,48.7743118379969 +13.4597528224969,7.56323751631851,48.3239781862407 +13.5004546363544,7.62690722013203,47.4124030567525 +13.5685278427015,7.65280494164838,47.6370152775013 +13.5805055777608,7.73704714295557,49.5950987833445 +13.7371077587241,8.00710231250642,49.7442768452663 +18.0696142518856,9.2491199852484,-46.5822141381743 +18.0950871421633,9.21591572054029,-45.6423351888752 +18.042419774284,9.00759206272001,-46.4301750987214 +18.0247209258916,8.82386515085991,-46.9547427387708 +11.5138402162161,7.25642760629752,46.6700359914856 +18.0644116025267,8.27174865143034,-45.689763424621 +18.0123587110873,8.24238437705774,-46.0899295921505 +17.9338281181901,8.59981602015187,-46.0834538609691 +17.9825893051997,8.37024214987371,-46.3357974014189 +18.0041148048823,9.04820932172684,-46.9777362568854 +17.9642067888598,8.83696384889571,-47.0142184188496 +13.6968732594366,8.31911229480762,48.926200881903 +17.9756536121198,7.88953475806891,-45.2648875123952 +17.7861469854378,7.60384509850453,-46.3288136251294 +17.6385207985197,7.47413104129464,-47.2234310578019 +17.6313290221131,7.58454685836797,-49.015145527147 +17.6965014978518,7.57706459476108,-46.4517564029578 +10.6914528695115,7.32776391095174,48.9429637270803 +10.7444541783494,7.20159297316554,49.969101548513 +10.7713030258288,7.168452955982,48.2676416617784 +10.793933209993,7.14641642200492,46.5974108942194 +10.8022735059297,7.10902947176844,45.7545803153403 +10.8114895231491,7.08252245353785,46.0625012541201 +10.8498277928191,7.00490167825827,45.4193018914361 +17.7258118345544,7.72803198494463,-49.7847295558789 +10.7369935129694,7.38119373681327,46.568045043827 +11.1968737932696,6.92101260069546,46.1226135052513 +11.1928359636406,6.92039210834963,49.0510239760239 +11.0643866132737,6.60445068814757,49.5939822698531 +11.0277311550279,6.57896465823435,48.8030048061507 +10.9734817581014,6.61384539871976,48.7871235868036 +10.8828304380321,6.73479524943327,49.0054157799887 +10.8283825762151,6.8376207256272,49.5858742277964 +10.860454704835,6.9226756715022,49.847451884087 +10.8449585344369,6.89714054976106,49.9976264679433 +10.9790901244379,7.0449758756727,49.8212855849507 +10.9967981295704,7.28228493244577,49.2642986918534 +10.9990213108142,7.17091403852848,49.6026808372945 +11.1034546592306,7.51919319312304,49.5175400607974 +11.0312966055337,7.39974114000715,49.4761396868532 +11.1579288007603,7.61291905047241,49.6114684265824 +19.9768444738514,8.53626081101187,-49.4138574522669 +19.6138522770599,9.33823785508602,-49.9680514406231 +13.3111895910636,7.08551540690147,-45.8529486173435 +13.3089630098874,7.07859870806234,-45.8282145518262 +13.2726499154321,7.09482615830765,-45.048092214809 +10.5184413443224,7.07163105162648,-46.0809565941814 +10.4897943605071,7.0301707158837,-47.2578655849986 +10.4631350143633,6.91693269705942,-47.8279750346666 +10.4461638358759,6.81075878274834,-48.2667932620583 +10.4422054314738,6.6887490759777,-47.930688612046 +10.4793221825884,6.54582355505524,-47.7192652107493 +10.5439757894624,6.3936700676912,-45.3022632184482 +18.8908412700325,8.98735087373193,45.4356902024033 +19.0403160867503,9.0212691797528,46.2217648833352 +18.5842217423705,8.76185488065253,49.515140233001 +19.2008311371863,9.11422986552159,47.429728756067 +19.2841666122322,9.16117718191624,48.2014874418823 +10.8848914523292,6.44889705684526,-46.0635173085162 +10.9395282232675,6.58250377699611,-46.8665375433103 +10.9683258717986,6.67793243971153,-48.6171958662886 +10.9941532825965,6.82929500770365,-46.9908243456343 +10.9902908682869,6.7870321712094,-47.3499902193526 +10.994181020155,6.85558106914849,-46.6473723426761 +10.9809902462501,6.83884243204402,-48.0963358084266 +10.9484853763445,6.79284913584971,-49.8615402759115 +21.0552464310794,7.26989056191404,-45.1837093635778 +21.0830458551477,7.56944408848263,-46.2798293662127 +21.0955997210217,7.9701124861572,-46.0611052711338 +21.0774772828245,8.22388313126726,-46.5165351833282 +21.0322122941261,8.29836015578367,-46.464553328611 +20.9287407739496,8.17683252591816,-46.615281787864 +13.5960232922,7.23726376032278,-45.2413712371003 +13.5977843165319,7.11678356771496,-45.0567986394834 +13.5201169014706,7.36640887770845,-45.3052605010067 +13.4525229376111,7.55556227083231,-45.4191433906112 +13.368173876708,7.73923080036502,-45.8489058963675 +13.325539800842,7.8684952821522,-45.8536862737341 +13.2433189732488,7.90351341557132,-45.4260796017344 +10.8862895835027,6.09003979275277,-49.1241491184098 +10.8423511989683,6.00231200847248,-49.1228304314748 +10.5685163326524,5.83876072951182,-48.9395843998745 +10.4880260660363,5.94742299692577,-48.3937834752383 +10.4184585855466,6.07714457551399,-48.22648580422 +10.3619381324937,6.09560841135251,-48.2323058798134 +14.3272926583099,7.17252854081823,-45.9889747765089 +14.3916389661771,7.13492282045499,-45.2407601607017 +14.2598500268584,7.24338311605472,-45.6014151354801 +13.9960124684696,7.6367125873566,-45.0398205015711 +13.9005282652737,7.92639071372242,-47.7237764826308 +13.9387918525593,7.81332944597807,-46.4667461630962 +13.8625067862446,8.10498301683756,-48.6158408009634 +13.8846035435396,8.05531089648,-48.2676878324829 +13.7710055561817,8.18853977316637,-47.9272212879601 +13.8480149392616,8.13814122909795,-48.4983129884416 +9.89650937655692,6.03190385320256,-49.5746635937313 +9.76561402682202,6.06786885299728,-48.7593004747397 +9.81983538173743,6.05081071281775,-49.0936555213293 +9.59802569915434,6.1994981751737,-46.5073065519431 +9.68894010971723,6.14897900759075,-45.4764341893405 +9.57166481453293,6.32183811918243,-48.9493115738004 +13.7477785567471,8.15773437986383,-47.616403216224 +13.7546377221748,8.19202346648514,-46.1440466875062 +9.77083463857558,6.59795022115777,-49.7969561430799 +9.70700835222495,6.59033712914125,-49.5728418497922 +14.8674395006821,7.9808960896506,-46.5311751754542 +14.8803861812445,7.86289974525759,-45.7072444587021 +9.8427243348541,6.74743648669327,-49.9105565841795 +9.99816071425821,6.72337200501536,-49.5514364903529 +10.0752385478858,6.68804731387345,-49.9485478891927 +10.1097900907223,6.66989161031609,-49.8312982314374 +10.1422884883137,6.66625555286219,-49.6422364614698 +10.1409006951201,6.6989060164789,-49.5474613320322 +10.1073000339878,6.76048110504155,-49.4597977055343 +10.0885509337724,6.83969064926799,-48.9570872133453 +10.0981462071938,6.95722411255771,-49.502614227371 +10.0581271184249,7.06904242119222,-49.9346899794188 +9.92333586370072,6.7205579072744,-49.3325992815999 +10.0368731889373,6.84293466501698,-49.3122428317293 +10.0353774192082,6.70549813250145,-49.489736899474 +10.0321049752666,6.50409469744566,-49.456913497103 +10.0202823952208,6.31388728280134,-47.803835618295 +10.0228355553582,6.28098464720358,-49.8458138945495 +9.98426055103067,6.38448009761627,-46.9808094907917 +9.94233646258663,6.55092294920822,-47.5285801381621 +9.88657256846303,6.67072111768033,-47.2109521303119 +9.84418393913421,6.73532195451298,-46.8806396345 +9.79903887051525,6.97213349740358,-48.7271989898574 +13.9894771328381,7.85227227160192,-49.8091663621721 +13.8962285626717,7.90696198062095,-49.3556170594094 +13.8166191083554,7.94153023551393,-48.5308779252893 +13.9540130528868,7.86144173180932,-49.6204372224917 +13.7458220990043,7.94015750804745,-48.4784368515683 +13.6964941536995,7.92293077375882,-48.3147131507856 +13.6743295813692,7.83556785994466,-49.100114690122 +13.7176462950211,7.86175703580899,-49.3847459644361 +14.1982257202792,7.46541957258468,-49.501900903069 +9.79771079310039,7.1058699824245,-47.1071827149166 +9.78634123018319,7.11005525529818,-47.4847181564731 +14.3283381135504,7.3737938552592,-48.7533224549692 +14.5474948252886,7.33398092718789,-47.6444794348374 +14.6918996643383,7.29746300009142,-47.3050222002185 +14.4547761337786,7.31737456474975,-47.8319737500931 +14.8851907673519,7.2897772148795,-49.0229550344698 +15.5818552532463,7.84801920751851,-49.9626586765102 +14.7708759470841,7.31341252615324,-47.5905055505511 +14.6339335073996,7.27120407362912,-47.1256298451169 +15.6591538023326,7.830025191307,-49.9987590840062 +15.623770693771,7.80860682358597,-49.508500339153 +15.6281705400552,7.96239502647247,-48.1555819174822 +15.6748543632518,7.97005132665048,-48.1484918878375 +15.7331817417822,8.03163736424706,-47.9046305611444 +15.807051670399,7.96446234083721,-49.1323325216894 +15.8939270304324,7.95087478119797,-49.4555628185902 +16.0882544248228,7.74822752621116,-49.6116795620015 +16.129728430686,7.6020145875173,-47.882327711564 +16.1947981756494,7.4903342738203,-46.1763329443801 +16.541873539965,7.35743390832031,-45.4577724601855 +16.6940098211587,7.43562402138853,-48.701377559093 +16.6107032364798,7.41292652101377,-46.9095841661793 +16.7370743783933,7.4916875909524,-49.8576684936276 +16.6979510792123,7.44594829253418,-49.7663296365434 +10.6920332791599,6.87513122576411,45.3401280805757 +10.5997307797203,6.85166924475053,46.6121753072127 +10.4692334422005,6.8286923031758,47.5885180165645 +10.361272666504,6.86899203016192,49.0170919272323 +15.5646838851745,7.61604964609124,-49.6435069798013 +15.5690368900237,7.6241248755702,-46.853283255914 +9.99112566325734,6.89651645113586,47.1860868708108 +9.98691656918826,6.8644729939369,46.0839001632497 +10.0184970388655,6.85411011322507,45.5956528710582 +9.97162686677326,6.9643700442657,45.3818026970653 +9.87910263914446,6.97704494306317,45.1160479739151 +9.80023817918783,6.93813355271617,47.8078837506508 +9.843023117492,7.00758701630438,48.8492813262338 +9.89422566470211,7.07741031188808,49.9769511156295 +9.99518027317869,7.21036445601219,47.6395381985355 +10.0105594063437,6.85842520829454,47.2630945157855 +10.2813174409225,7.21041835714166,49.5657601233199 +10.315974617199,6.90412713959317,49.9729632761936 +10.4933061934449,6.68844922436207,49.1092426858944 +10.4899845262976,6.7076089760352,49.058123297563 +15.5583873474812,7.54670647014076,-45.1424933806885 +15.3326239164212,7.54459364405027,-45.9689786821216 +15.2562062896426,7.51092302390735,-46.5176428692378 +15.1209713398749,7.4491381619782,-45.6638156097954 +15.2036563152278,7.5345522120392,-47.0998229165048 +10.273855834606,6.60725104837026,47.6728871730988 +10.2199042670798,6.62708001010706,45.2538048309503 +10.2963515668028,7.0455262845065,45.1092205699405 +10.2868347274386,7.04696122983488,46.319914024583 +10.2759114781728,7.10707076349607,46.8083228572954 +10.2646315762627,7.12755295810474,48.3553003928679 +15.4626628029212,6.5747526191773,-46.4379776225695 +15.3803998679836,6.35817245177134,-47.6137169002687 +15.4185489840907,6.50430070671862,-47.417046353509 +15.305929847822,6.40595233537715,-46.8691845938791 +15.3243759700668,6.40891990038936,-47.1179868016948 +15.3072014211764,6.43045092248753,-48.2052078384264 +15.3075940164661,6.44090843923469,-47.9469083380246 +15.3224182608385,6.40642322822394,-46.0213388177064 +15.3123178758337,6.41353510496401,-47.822912387254 +15.273051697436,10.5278049024716,45.1049936262802 +15.2461532767894,10.3427952350305,45.305035256379 +15.2249650079632,10.0309428508346,45.9116468546214 +15.2433581004298,9.63285461890445,45.3015283857212 +15.183463218666,8.11041273116113,45.1936482774751 +15.0893944257185,7.56184391351078,48.2417464827944 +14.4156111742532,6.35421377316894,49.4532391432405 +14.4842725401479,6.07653104163128,48.6766048811156 +14.4432374162501,6.22506705538116,49.6390830805034 +14.5770976871965,5.87952154542738,45.3799917842534 +14.5364046246607,5.95419706287457,46.8867038112893 +14.4204844507956,9.841322090465,45.7182570502533 +14.3095603739362,9.54981648506327,48.164600829553 +14.3614591303377,9.67025597673351,46.890073981461 +13.5972755460886,9.69714032266545,46.9161935267504 +13.5963957124221,9.5983582073541,47.2011225918252 +13.6151259978781,9.56217285454464,46.4394057534747 +13.3301057792983,10.6496383099489,47.0003071568332 +13.3458238675777,10.6443606536814,47.7334808339115 +13.4369711181215,10.7288401077602,48.1508903204691 +13.5024617668321,10.8296903619618,48.2924952770129 +13.6441583433379,11.1206182374318,47.354883382677 +13.6817292755839,11.2947545550377,47.3547829421733 +13.7315894708094,11.3900653933044,46.7365180899399 +13.8406184696387,11.4381175089121,46.0786105440788 +13.8348174975723,11.3087675656467,48.1018127677371 +13.8664194423417,11.2602035390288,47.9859071658947 +13.8902144294514,11.2250610197746,47.9276480178973 +13.9338470712082,11.1061981602135,47.5135228602954 +13.8166573064446,11.496049438837,45.2824272547022 +14.0201095478744,10.9642754821954,47.239847340179 +14.0343172907223,10.9054770955029,47.5124073750088 +14.0207686795785,10.8099074774062,46.9819854778186 +13.5646330799454,10.9379019369885,47.5839055305619 +13.9518413276424,10.6960426760235,45.4216879842371 +14.00570338129,10.7134971247557,46.5344313556325 +13.5537389271067,11.6922718395143,48.0801764009139 +13.341895116391,11.7982627333382,48.2336459309942 +13.2466843599119,11.6719474633561,47.1155545883161 +13.1733497517324,11.4403256432455,45.3228535086006 +13.4360253988304,11.8525037452307,48.6348180856946 +13.625048994749,11.3353324798298,47.4015506167814 +12.7977366956583,11.8729516266834,45.1902298024456 +12.6471691786415,11.2984912558669,49.2022249046589 diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java index f0172f92d17..0e44953b422 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java @@ -3,6 +3,11 @@ package com.sap.sailing.polars.regression.test; import static org.hamcrest.Matchers.closeTo; import static org.junit.Assert.assertThat; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; import java.util.Random; import org.junit.Test; @@ -67,6 +72,64 @@ public class IncrementalLeastSquaresTest { assertThat(coeffs[3], closeTo(0.2169 , ERROR)); assertThat(coeffs[4], closeTo(-0.01182 , ERROR)); } + + @Test + public void testIncrementalRegressionWithRealData() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(6.319899225, ERROR)); + assertThat(coeffs[1], closeTo(-0.427890096, ERROR)); + assertThat(coeffs[2], closeTo(0.057763706, ERROR)); + assertThat(coeffs[3], closeTo(-0.001574209 , ERROR)); + } + + @Test + public void testIncrementalRegressionWithRealDataNoIntercept() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3, false); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(0, ERROR)); + assertThat(coeffs[1], closeTo(0.9317759507, ERROR)); + assertThat(coeffs[2], closeTo(-0.0364029315, ERROR)); + assertThat(coeffs[3], closeTo(0.0005326266, ERROR)); + } + + private void fillRegression(IncrementalLeastSquares leastSquares) { + File file = new File("resources/5O5Worlds14_Winners_Upwind4550.csv"); + BufferedReader br = null; + String line = ""; + String cvsSplitBy = ","; + + try { + + br = new BufferedReader(new FileReader(file)); + while ((line = br.readLine()) != null) { + + // use comma as separator + String[] row = line.split(cvsSplitBy); + leastSquares.addData(Double.parseDouble(row[0]), Double.parseDouble(row[1])); + } + + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (br != null) { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java index 06ac9410c4e..7a824bfc331 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java @@ -44,10 +44,22 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua private PolynomialFunction cachedFunction; - public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder) { + private final boolean hasIntercept; + + public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder, boolean hasIntercept) { + this.hasIntercept = hasIntercept; this.polynomialOrder = polynomialOrder; - matrixOfXSums = new double[polynomialOrder + 1][polynomialOrder + 1]; - vectorOfXYMultSums = new double[polynomialOrder + 1]; + if (hasIntercept) { + matrixOfXSums = new double[polynomialOrder + 1][polynomialOrder + 1]; + vectorOfXYMultSums = new double[polynomialOrder + 1]; + } else { + matrixOfXSums = new double[polynomialOrder][polynomialOrder]; + vectorOfXYMultSums = new double[polynomialOrder]; + } + } + + public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder) { + this(polynomialOrder, true); } @Override @@ -56,13 +68,15 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua try { numberOfPointsAdded.incrementAndGet(); functionNeedsUpdate.set(true); - for (int i = 0; i <= polynomialOrder; i++) { - vectorOfXYMultSums[i] += y * Math.pow(x, i); - for (int j = 0; j <= polynomialOrder; j++) { - if (i == 0 && j == 0) { + for (int i = 0; hasIntercept ? i <= polynomialOrder : i < polynomialOrder; i++) { + int powerI = hasIntercept ? i : i+1; + vectorOfXYMultSums[i] += y * Math.pow(x, powerI); + for (int j = 0; hasIntercept ? j <= polynomialOrder : j < polynomialOrder; j++) { + int powerJ = hasIntercept ? j : j+1; + if (powerI == 0 && powerJ == 0) { matrixOfXSums[i][j] += 1; } else { - matrixOfXSums[i][j] += Math.pow(x, i+j); + matrixOfXSums[i][j] += Math.pow(x, powerI+powerJ); } } } @@ -115,7 +129,23 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua } RealMatrix inversedMatrix = new LUDecompositionImpl(matrixOfXSumsCopy).getSolver().getInverse(); RealVector coeffs = inversedMatrix.operate(vectorOfXYMultSumsCopy); - resultFunction = new PolynomialFunction(coeffs.toArray()); + if (!hasIntercept) { + resultFunction = createFunctionWithZeroIntercept(coeffs); + } else { + resultFunction = new PolynomialFunction(coeffs.toArray()); + } + return resultFunction; + } + + private PolynomialFunction createFunctionWithZeroIntercept(RealVector coeffs) { + PolynomialFunction resultFunction; + double[] coeffsNoIntercept = coeffs.toArray(); + double[] coeffsWithZeroIntercept = new double[coeffsNoIntercept.length + 1]; + coeffsWithZeroIntercept[0] = 0; + for (int i = 1; i < coeffsWithZeroIntercept.length; i++) { + coeffsWithZeroIntercept[i] = coeffsNoIntercept[i-1]; + } + resultFunction = new PolynomialFunction(coeffsWithZeroIntercept); return resultFunction; } From 2c7f3c4174ef23298dc74b5475bd54a8f6706132 Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 17 Feb 2015 17:13:12 +0100 Subject: [PATCH 143/264] added gwt.theme project to all launch configs --- java/com.sap.sailing.server/SailingServer (No Proxy).launch | 2 +- .../SailingServer (No Proxy, Cached MTB).launch | 4 ++-- .../SailingServer (No Proxy, Jetty on 8889).launch | 2 +- ...SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch | 4 ++-- .../SailingServer (No Proxy, Jetty on 8890).launch | 2 +- .../SailingServer (No Proxy, Remote Debug SAP VM).launch | 4 ++-- java/com.sap.sailing.server/SailingServer (Proxy).launch | 2 +- .../SailingServer (Proxy, Jetty on 8889).launch | 2 +- .../SailingServer (Proxy, Remote Debug SAP VM).launch | 4 ++-- .../SailingServer (Proxy, SwissTiming Live Simulation).launch | 2 +- .../SailingServer (Proxy, winddbTest).launch | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 8a9f6b5f5a2..21627fd6acd 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -34,5 +34,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch index ded8c572ff2..68dd7191370 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch @@ -29,9 +29,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 4a87c5dcac8..3ec0f77dfc2 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index 3a7fa0de9c5..7dc838a1181 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -16,7 +16,7 @@ - + @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index 254ba26fee7..4e7742996a4 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -26,6 +26,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index 723697b6c4f..a0b813374ec 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -15,7 +15,7 @@ - + @@ -26,5 +26,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 73c1d33df86..6d252d818a5 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -34,6 +34,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 49611a87c36..6d2ff20591f 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index 9e99a5d1946..90ea164e7fd 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -16,7 +16,7 @@ - + @@ -26,5 +26,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index 59df666c599..cfa760728db 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -26,5 +26,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 1851792927f..0f9e209026a 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -28,6 +28,6 @@ - + From 46cd5346763ee11a3ef98beb1f9ffd4d03f37a6d Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Tue, 17 Feb 2015 17:14:40 +0100 Subject: [PATCH 144/264] Added symbolic inversion for cubic least squares --- .../test/IncrementalLeastSquaresTest.java | 61 +++++++++- .../IncrementalAnyOrderLeastSquaresImpl.java | 106 +++++++++++++++++- 2 files changed, 162 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java index 0e44953b422..55e87027925 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/regression/test/IncrementalLeastSquaresTest.java @@ -87,7 +87,7 @@ public class IncrementalLeastSquaresTest { } @Test - public void testIncrementalRegressionWithRealDataNoIntercept() throws NotEnoughDataHasBeenAddedException { + public void testIncrementalRegressionWithRealDataNoInterceptWithSymbollicInversion() throws NotEnoughDataHasBeenAddedException { IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3, false); fillRegression(leastSquares); @@ -97,6 +97,65 @@ public class IncrementalLeastSquaresTest { assertThat(coeffs[1], closeTo(0.9317759507, ERROR)); assertThat(coeffs[2], closeTo(-0.0364029315, ERROR)); assertThat(coeffs[3], closeTo(0.0005326266, ERROR)); + + leastSquares.addData(0, 0); + long startTime = System.nanoTime(); + leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("With symbollic inversion no intercept" + (System.nanoTime() - startTime) + "ns"); + } + + @Test + public void testIncrementalRegressionWithRealDataNoInterceptWithoutSymbollicInversion() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3, false, false); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(0, ERROR)); + assertThat(coeffs[1], closeTo(0.9317759507, ERROR)); + assertThat(coeffs[2], closeTo(-0.0364029315, ERROR)); + assertThat(coeffs[3], closeTo(0.0005326266, ERROR)); + + leastSquares.addData(0, 0); + long startTime = System.nanoTime(); + leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("With numeric inversion no intercept " + (System.nanoTime() - startTime) + "ns"); + } + + @Test + public void testIncrementalRegressionWithRealDataWithInterceptWithSymbollicInversion() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3, true, true); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(6.319899225, ERROR)); + assertThat(coeffs[1], closeTo(-0.427890096, ERROR)); + assertThat(coeffs[2], closeTo(0.057763706, ERROR)); + assertThat(coeffs[3], closeTo(-0.001574209, ERROR)); + + leastSquares.addData(0, 0); + long startTime = System.nanoTime(); + leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("With symbollic inversion and intercept " + (System.nanoTime() - startTime) + "ns"); + } + + @Test + public void testIncrementalRegressionWithRealDataWithInterceptWithoutSymbollicInversion() throws NotEnoughDataHasBeenAddedException { + + IncrementalLeastSquares leastSquares = new IncrementalAnyOrderLeastSquaresImpl(3, true, false); + fillRegression(leastSquares); + double[] coeffs = leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + + assertThat(coeffs[0], closeTo(6.319899225, ERROR)); + assertThat(coeffs[1], closeTo(-0.427890096, ERROR)); + assertThat(coeffs[2], closeTo(0.057763706, ERROR)); + assertThat(coeffs[3], closeTo(-0.001574209, ERROR)); + + leastSquares.addData(0, 0); + long startTime = System.nanoTime(); + leastSquares.getOrCreatePolynomialFunction().getCoefficients(); + System.out.println("With numeric inversion and intercept " + (System.nanoTime() - startTime) + "ns"); } private void fillRegression(IncrementalLeastSquares leastSquares) { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java index 7a824bfc331..32f8dd8a7b4 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java @@ -25,7 +25,7 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock; * */ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSquares { - + private double[][] matrixOfXSums; private double[] vectorOfXYMultSums; @@ -45,10 +45,12 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua private PolynomialFunction cachedFunction; private final boolean hasIntercept; + private final boolean useSymbollicInversionIfPossible; - public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder, boolean hasIntercept) { + public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder, boolean hasIntercept, boolean useSymbollicInversionIfPossible) { this.hasIntercept = hasIntercept; this.polynomialOrder = polynomialOrder; + this.useSymbollicInversionIfPossible = useSymbollicInversionIfPossible; if (hasIntercept) { matrixOfXSums = new double[polynomialOrder + 1][polynomialOrder + 1]; vectorOfXYMultSums = new double[polynomialOrder + 1]; @@ -59,7 +61,11 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua } public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder) { - this(polynomialOrder, true); + this(polynomialOrder, true, true); + } + + public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder, boolean hasIntercept) { + this(polynomialOrder, hasIntercept, true); } @Override @@ -127,7 +133,14 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua } finally { LockUtil.unlockAfterRead(lock); } - RealMatrix inversedMatrix = new LUDecompositionImpl(matrixOfXSumsCopy).getSolver().getInverse(); + LeastSquaresInversionHelperType type = LeastSquaresInversionHelperType.getType(polynomialOrder, hasIntercept); + RealMatrix inversedMatrix = null; + if (useSymbollicInversionIfPossible && type != null) { + inversedMatrix = type.inverseMatrix(matrixOfXSumsCopy); + } + if (inversedMatrix == null) { + inversedMatrix = new LUDecompositionImpl(matrixOfXSumsCopy).getSolver().getInverse(); + } RealVector coeffs = inversedMatrix.operate(vectorOfXYMultSumsCopy); if (!hasIntercept) { resultFunction = createFunctionWithZeroIntercept(coeffs); @@ -159,5 +172,90 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua } return new PolynomialFunction(coeff); } + + private enum LeastSquaresInversionHelperType { + + CUBIC, + CUBIC_NO_INTERCEPT; + + + /** + * + * @param realMatrix matrix to inverse + * @return Inversed matrix if symbolic inversion was found. Else null + */ + private RealMatrix inverseMatrix(RealMatrix realMatrix) { + RealMatrix result = null; + switch (this) { + case CUBIC_NO_INTERCEPT: + double[][] originDataCNI = realMatrix.getData(); + double a = originDataCNI[0][0]; + double b = originDataCNI[0][1]; + double c = originDataCNI[0][2]; + double d = originDataCNI[1][2]; + double e = originDataCNI[2][2]; + double[][] matrixDataCNI = { { d * d - c * e, b * e - c * d, c * c - b * d }, + { b * e - c * d, c * c - a * e, a * d - b * c }, + { c * c - b * d, a * d - b * c, b * b - a * c } }; + RealMatrix intermediateMatrixCNI = MatrixUtils.createRealMatrix(matrixDataCNI); + double factorCNI = (1.0 / (-a*c*e + a*d*d + b*b*e-2*b*c*d+c*c*c)); + result = intermediateMatrixCNI.scalarMultiply(factorCNI); + break; + case CUBIC: + double[][] originDataC = realMatrix.getData(); + double n = originDataC[0][0]; + a = originDataC[0][1]; + b = originDataC[0][2]; + c = originDataC[0][3]; + d = originDataC[1][3]; + e = originDataC[2][3]; + double f = originDataC[3][3]; + double[][] matrixDataC = { + { -d * d * d + 2 * c * e * d + b * f * d - b * e * e - c * c * f, + -e * c * c + d * d * c + b * f * c + a * e * e - b * d * e - a * d * f, + -f * b * b + d * d * b + c * e * b - c * c * d - a * d * e + a * c * f, + c * c * c - 2 * b * d * c - a * e * c + a * d * d + b * b * e }, + { -e * c * c + d * d * c + b * f * c + a * e * e - b * d * e - a * d * f, + -f * b * b + 2 * c * e * b - c * c * d - e * e * n + d * f * n, + c * c * c - b * d * c - a * e * c - f * n * c + a * b * f + d * e * n, + d * b * b - c * c * b - a * e * b + a * c * d - d * d * n + c * e * n }, + { -f * b * b + d * d * b + c * e * b - c * c * d - a * d * e + a * c * f, + c * c * c - b * d * c - a * e * c - f * n * c + a * b * f + d * e * n, + -f * a * a + 2 * c * d * a - b * c * c - d * d * n + b * f * n, + e * a * a - c * c * a - b * d * a + b * b * c + c * d * n - b * e * n }, + { c * c * c - 2 * b * d * c - a * e * c + a * d * d + b * b * e, + d * b * b - c * c * b - a * e * b + a * c * d - d * d * n + c * e * n, + e * a * a - c * c * a - b * d * a + b * b * c + c * d * n - b * e * n, + -b * b * b + 2 * a * c * b + d * n * b - a * a * d - c * c * n } }; + RealMatrix intermediateMatrixC = MatrixUtils.createRealMatrix(matrixDataC); + double factorC = 1.0 / (-a * a * d * f + a * a * e * e + 2 * c + * (a * (b * f + d * d) + e * (b * b + d * n)) - c * c * (2 * a * e + 3 * b * d + f * n) + b + * (-2 * a * d * e + d * f * n + e * e * -n) - b * b * b * f + b * b * d * d + c * c * c * c - d + * d * d * n); + result = intermediateMatrixC.scalarMultiply(factorC); + break; + default: + break; + } + return result; + } + + /** + * + * @param order Order of the polynomial + * @param hasIntercept If the least squares is set to no intercept + * @return type of the polynomial or null if it doesn't exist in this implementation + */ + private static LeastSquaresInversionHelperType getType(int order, boolean hasIntercept) { + LeastSquaresInversionHelperType result = null; + switch(order) { + case 3: + result = hasIntercept ? CUBIC : CUBIC_NO_INTERCEPT; + break; + } + return result; + } + + } } From a964dcda4750010b9df13f8a0fac631ce3996ce9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 17 Feb 2015 18:12:00 +0100 Subject: [PATCH 145/264] require authenticated user to create UserDTO --- .../src/com/sap/sse/security/impl/SecurityServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index 830403f7b44..778ef3efb92 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -631,7 +631,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica public User getCurrentUser() { final User result; Subject subject = SecurityUtils.getSubject(); - if (subject == null) { + if (subject == null || !subject.isAuthenticated()) { result = null; } else { String username = SessionUtils.loadUsername(); From 02d54a725fe2ae0db9d338ec4026cf17796c54cd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 17 Feb 2015 18:20:13 +0100 Subject: [PATCH 146/264] upgraded TracAPI from 3.0.9 to 3.0.10, hopefully fixing bug 2630 --- .../META-INF/MANIFEST.MF | 2 +- java/com.tractrac.clientmodule/README.txt | 15 +++++++++++++++ .../com.tractrac.clientmodule/lib/TracAPI.jar | Bin 765744 -> 765760 bytes 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index b10a81fdeaa..900b4166852 100644 --- a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF +++ b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TracTrac Client module Bundle-SymbolicName: com.tractrac.clientmodule -Bundle-Version: 3.0.9 +Bundle-Version: 3.0.10 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ClassPath: ., lib/TracAPI.jar diff --git a/java/com.tractrac.clientmodule/README.txt b/java/com.tractrac.clientmodule/README.txt index 08aed822c98..a8c6776fdc9 100644 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -16,6 +16,21 @@ It contains also some files: - test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example. - Manifest.txt -> manifest used to create the test.jar file +******************************************** +************* TracAPI 3.0.10 *************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 17/02/2015 +Build number: 9938 + + 1) Bugs + + - It fixes the same bug described in the release 3.0.9 that it was not fixed. The release 3.0.9 fixed + the synchronization issue between different threads but the ConcurrentModificationException happens + when the same thread is accessing to the list. The synchronization at method level doesn't work + in this case (Reported by Axel Uhl,07/02/2015) + ******************************************** ************* TracAPI 3.0.9 **************** ******************************************** diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index f3d5016f5d8701a8b5eda0eedd0cce3e1c4d1112..7a33f5cd105e7e59e972181e8dcc50eccf1178ed 100644 GIT binary patch delta 45432 zcmcG12T)W?(*iiY=4<$t@F!Ts|5uJgt{tHsZ`~9 zpWsBbP6GT-?}XTA-A}(>zkWt-@R~jC{qpatDIWPOI5SZ6T{vw**xVy)4sP4Uv=0}R zY3S6f$hNiWK3QCQQWE2nwZi>W!NWR@%Jeq}OzS>8o7^j=P*}9zF6F%T!Ar4GGw%Fs zJLlVcn_XK6-1ru(=W?sXaI0f>&)50d=MR6Ap>`uF{?^L=>*AwUl!Wg)>uQ}N<+c1a z{Nb;j+B^I9^t;Qpvn$FT=Js;3+QrNJmTSJd<~OE0b7)d#;Lm;Y{9akbxQ^RfY45o9 zeT-(9;6{fZ!k_?&^vKls)y_N>mEL?66(v&@CwOkyRz($$*sMscFh@T9Bx>k$SH3C6 zAoYi0N5xLQB_S)|4!-N^kmLv!2FxCuko&8N3oj5HZEsgF;(!&{wazwIX-F|Y08@TGqTe-r1i?=v0H(Bvs$TBXigALKHx zNBFNkRTtMj%(-WeNds<{eLufK-eyF?>|-X^KBY7Fd=IwWtnuXO$IUwLO}$iiREK}t zZm{8V?ZsBNe21=Fp6(TO!?yI`#F_8CzOVF;by$SIlggiHrVU*SXQl- zb#l?fIfY(NKC~D+ug{b-qa`awZ#Xf|JUYTRaQdZ%lM+Pc9{kAz;x5)09z1>EaAGF& z?(3m}|L)bj+I`@uM?;bg)CSwRADgwCziXto>a0rvzy0jyMNO@ZkpH&nq;Jc+a9_H7 zsYhl46|uNS!P%zSfHe@yP^*~20?MC{4#AT9E8soMFVr^B2<*EhP|sf}muu6kUQ* zvMlWVv^TG(e#t23eTeGUJ+xauq))oZRI9apwO@8te~2bsxH>0xs{1v*S&QmjA*Lq$ z0ms=Z`NiQ2QI>Y)gBl*1W!u8V`$y~a$~W@|Ed6HhUub&GSn6`Mc&Jh4A&+k1O)IgRb6Ym5Z~~N+z{=^iSVC3lEKNJ9_c)Nw+#r{w|#HHP6jy{$2O8 zr_P(+l=i(9>X6Cg#rd9}9$WSA%||M8H7lN%`W|z#c<6fRXy=4rx%ZL1cMC=T?ktWw za3k!lEN0u*OgEQZn{G|dGF1e8csc3gpdQ;|YGxfs@&B!rGT(V!+Lm@5C!G}>*G;PU zXULNbo0ORs;=gtpeDiMAnsZ0@tlD$&Rr_7jyzA$)9YWu}?-Ehd`Av>Py!c$-)3F8m zY4Vd5{nKh{s@$J_`?y|Jf<6s7Iq{74vyB(~4!Mz6zA#=~awcc#!&gxenJ+K;L=K$j@k@fUQJC+E zWsX7>6*tX4vQbYJ6-A={SY37dzJsCy`u2BiJH$0Jr_J%qHf@F|x?1S-+mLs{#%g(hsK)#^jdxWJv%3sS!(CQ;Jp=TG^ccW~A4INR< z8b%XcTFdBV4{>qVqQq;pg@V_@#86MBLm!WQ=xhWW?Ehdh{pY>{LIQ zlu+0HgvqH{q?Y|6XgVDq&Ig-Px*9891ZijQ8N87~dAbq;&7LvhDm9`eTWF3Fk{Ofi z=ul5Wq)7E^n_U&TfK)5gh)2bVP{lGPstT!owo=OJ^VEDw>DG=yutbCct|=o*P#&NyOgfhXo`YS%hrySaXhFqj8Tz-(aIjTd@PN!2B%3^PawlV#SYjNI@xE| z-=HITlNoC726uHu>Bc+CO1|85m5?b4?1}poK!u)c0)xamE;MOG9D&Azp{Q>%Bh5D1 zZA0g?>s}#s7jYoOC;F8Pv$byD6gtgaK9eZbLZOI}p-G;+#>u9TT*T;R?>g|JnU$D{ z>1M__yEvmGmNBW1Y3tvwlGF0a0_FQJuEMh&S%g@9)Y`O zYo2RG?MUbgQ;fOTZDlO76D}UC*e_VwDiVkJN@xR%d%2Fw(e|zyeDT>OZ$Ym$PAg@q?p3Wbs4RS}8!pEYwbz>S^ z^?1Scx&M{M5HDJ4p7c?q@T{P-5K_^Nh)u2NMf53{E`z<>F2+2&pMSD236?uCIPd!?1vFe)QuLN`bj1eY!#<@aFRzktNGW z6u&>Q1cC!3>61FjOof=M{TxZx{`9YeYxa_QX_Fo(;T%r`joi(s9=pi<+>H50b+(ev zV`)Yp;y!92^*xLl`&7)!q6|2WY`%9)X>$vj5<5ViRVo@ab#~>^NJgXflFfcF}mze7v6HJDzOw)&2;1C5-+h3 z*B5BG;cSQ!0(kmOOC;FP97V2%a_Cb&yeI zXZPoQq!y?8Q8;^e5U;*DTReKwA+Y<$P@afpO=1Y<@#qkv$qoqR{iFl%Dq(^7AgVO3 z6I6P}VVF712|N#)!f{3#X(9bN@tAO>C1Qvn}UrB z4d2R>BBxcL&%CvpyEA%EScq2a6{ zXd?bb!Vnh}IzC|=k7SNN1*&H;8tj$rycjAFxOuWGcJb8yL%s&uk;qd=8JUb2Rj2am zh=hivps|M+LQ`{BAw$-}u7kXbgoLg$H8ehl(PH-=;+>=MSWNQ)yD5X0r%DJZL|rlM zg$P+3V;D9emsdw^qp3j`ojl7s-pG`NWj>B7iZf=?M=jR=A}@-vCyLQ@A!ct};e`;e z0`d)HC3Zv+ZxfxduGV~#XiQM25?*?fO`p_RgPXinS_EQoA3`qw8PAn`gCT8@>j{Y6 zj?Z}xgbhvr$VbRGLAze?t`RCy_6g{pm%Q&7q<|Pg2~|83^!QbCB4Dh`mc8aB(on(M z8BF%3Nm$$|5O%hjXH6+!mOy8QFJX;pc_GvTB=|8W6jZ>dq0oOJ@tpt2`$(-MOPONx zV9k8{)G*tAm(z@d?ac7GY>cIXB9^SXIzNKS<3Pr2 zkv?COPG3Zp`Ee7zv9hBq&~S794WbY6jL#Xy5V_=oQVT8kXNgiQ=sx`~^YS27eYEBC zXv6@CHyT+0T6sC}GaIKB=XGl*ejeeUsF0Z_JM-y9223Y>)T%47Lu9lv=2T8^44w!^xt7ffmX*$7r*GZD0gOZW_)wBOedGJ)w*V5qO|U z=OGCn@!)r+V$20Vk$;{sA=N(ik801iwpm`RIZk9F=r$nz=PKeioyF=Pk z)`<=6!5>aN279E4|2k3ihZB{bO~{Cb<6UY8{el0zFM<Sc^zda3C z_%xW+9L(=PZNOiV+E)nD14H?bnqchX5dNHIU(-Sx!m(>6I12RYzy?O}3n@vhgU}Ua z_UG%fVWauAje>mHP2>5Ijb8^N?;;R1bTYr5J0m$!tJ|~bv-#5uh$qNA5cYG{^GDIK z4X_rhSt8$+z@Sd?xveU(s5j!DqzLggzCSJO#I_wbL1e~mK9|_Zww-wO`%OlV9k-AF znfj1!PQZukmP7m_)GV^a@hRk6H=aOIgQYU$a}Q3Y(@J5HeLuo~)3DFUvVWS7`sX;m3pJ1EM8w43fh6N`ivLE9_~O5W%_`)#p*8GptP#tmH+rmJ3ix9Rc@b``Y4a2c|E48_9x5mc>SY64nCSlH@xqf8*#atpD6Scg5Y zCCH|x<81?7rr~AH?j;b!Tss6HJ=Q{Bu#!pyLkP@@{cXU>LBw@LMMCUiEO<=;LZD&5Oqd=E6kMjNEDjQc6W>1%5?rU;pU!8D(TxFuzWR6@zL}$%*Ng>wFHG=?P6!Ex@<>5PG7YAq1sT*nm{nJlkR-5V&BqBg zXk#qCUqC6B1e)mdcSeqK-!jbc`GSpf<4r4Kq`(aQz9ukZ-^U5&QUOjH!f;BVTK`^z1BqfYsmmLt4G^HJIi3z(iT`-k;0^i)B2JW!gKPGS|$8tv7i_?ci0`rYJ?6{e$!08{R%TVMkYK@{R}aApDICe zY?F%B-yjr5>Im!q6C3s&u-OJex|fHeMpG0Uu+b*M?!>8VzJ;)ojwg$YEX&Lvj1axC z;qKx|-5~IU->?N6&_cM4#10Gbe2xwUo7y8Xs^%wShBRD2V+d@7okLf815%&$bQMmb zdjMKDaUQ@}_Jx-)olZ1yMUy5$_27>(Ut!B;b~a&;)x+(<$ezM|Bn{$a&${{vS8}&D z_*CDZyCtg?BwRwngPa^2nQW5E)!8Tgg(51S1XX#oK+ZbpbvaeTQ2TdxqlqMC6l zjc6u8H-vJSo@C)Rt%lW3)$SmYo3X7A2Z410?}dh(}Xi< zs)*NjMnNwaZL~-%*CWTS|EQ4d2H*uP#w8uax>8{Q`nc_+~BzZ#aYVaV)R=g*FqJU!dCZn*Mmmu?OVG*H#ON-Bx^oh_3r7%E{076*%V?rO| zUDU*bV5?-;~Vrg0Msxgu1AK zQZ8#{Qg-wup@FiEx@=~l(1XI@a*$(Uaz)5JzG{RapcAoEt_ta`Cw}C^sSmn}*=;w3 z$&^dO^yso66P^Yz5(hPTvw%5FbFo;Fy3Va<=@o(1J2);_jy@*I|2=3h&TG z1YQfoL)obNLhh=Tc&+$>@D_C|z88jXdMKnqC`0*LFtx8A3UxUX@x>^?>T5$xb$cv) zK}G?$PH2&~T+T+730o6d7LHm{Ito6yvro!}J?U3S7_Hc#3ZW_e+AwSC?6zmZWwc_$ z9USs`BP6eBAV>yIDDaI?j^gy>6888TVRtf;+}r|{5rHEMJqQoKO5sIfA9f8Vu3DML2i>jVqMw}E34G)yp-i!xp4*8W0?jj!Yopd5gfEHF88qu!oH`lnpX#>0g9ZO&l~& z2!#khuxl`amG<_lu$myr%mZA8G`QgV&M*DsUwPMvO&Fx0_ z@2jvI#6NQL)P!l9rmJqgXmQjmW$X7RbE}tW8ct}RBT0yPe=@A)E4$}&T&|A3dPHY< z-&=0R`(#7<82BF9|K`g|m;0CMJS?;$Kdo@zr84~Fgj1i(`nV>~x~jK(#nE=X9;Da& zy4$fnuBTN%_rTAAIjhm6H=h{4V?xiE zs<+oC`df^8zvjW_9;QncY}Jr-f4c;EzdiWzwe{EYV#A4fL;JsczsB>_(f&?tCJGD8 zG|F{1dG3;QPqKB-%Dm0IEC2bXKEC|5NVl%8`;h#Jr&E*5pXn_W&ArCxthjDb>M69F zzhz#Bi?LUpo^j}CePZo8f9E0Qfr6t)Pqiu5yrms2zh1DRd}%w&*4JlxT#aknyKq{Z z!Y$#b*N4RqC!TjRD(T}WIwXyoduDr$>7e7?dsPPXby(56Lz}xYX|3qVhyEs0y?;;I z9gt{cvuw6|)z^v*#VWFAx4R|yc9?cyH)H>DV2K`*1a{jR8)c{&{9MxCJ$&fIO~a

    -u`D(zI{%YBJYR!p5RSm$2dtuBIv`_oUIDGncLzM^`PmbC2%z0Ay%6DvwC z){K~2x8Esn7EkZ{X!F&W*rTQzTPj2U^aceGW;+ff7TpZ>Vo z{@v9vUUTcWZmntU+dsSIl;VKc7vhAHqjRh7R?ba7W`1_k0-46)(w;LX=$X}hI&u4R z;wFCG^WE_U2ETqyDjB)%Oz8TxR~K(Rd)LK(Twig9bMJWzj@@F%rP%H~hny|y#rXAx zSZ(K-!{PNUx#Z76l{g3P93lAJ{-f@>;gccv-h?SSUoX@#lE&%uoD9`&+Funf0^gq% zYl<|G*>5htL-J~c;?zV8n%V$@)Ive*ncu?EWb;7o(8<6>rcl76#3JsAFxhuu1iM^K z^b*bH zc1gI*Hz8uWi$>EY*^nI|`RIz|D9;i$Lry^=>Kf#8RpiWqv>tTt4JoJJN%wGAG2#Yx{Blve1Ic55*zJ@l($ymfGB;7bM0)5%8aFK@7h@1xctOOK ze*BaHgEsXOGi-68XbRDb1ySqi38T1J^pzUi6b4ZjU{>oNiS#IfOgG0w#`b?LvZJ1( z_m>=VG_s}KioIVW8bl@F9WmW}{=s+oFVRNooJM^?xx2(DG8YisB6Nf&mZM>BxS)sq zE1hH(;4esUHu)Vurl{CnE@NMb#D}PR8wXHEtsUg%>|2TWJB@4n{sWB}Pc5+wnW>6t zT$9~3nJo-wXQ+yw5KWt80o;(?q9LA1#E_zh=~;k3qwz?}r;nl7oft%>wAdK}=9r<*Xg5jI}j~*So zUh7a{^6GW1e1|wG=C58^GEUPbnd z6%PA8x29~n#oSe{E+1?%>=Q=#Cr;eoLS=84@bha`Dwnk%=yj+1gk4CISiN%D)^R?M zr^;ogRJ$!~^`(D&e(I>*ak3TaY5qq@#IuiX zqp^=3S9v+@U6f+Ga+v#~LYpVvF`f71wF}K_e`9KVn)=UpwVMyDf}iM5ugtSQ(^243 zUsW%8zBD!{z(gW7t-o=3B~-?a(myL>nUgr|zl!3Z=0$Otvsl-t&{?cG1qvd*OjJGw zW;u(=gGKbhS?oa;K+SovG25ZFcrRIk?31?Q7W9+2vpA71`O`arB&Un`16^z7tHs9X zlq;m)+ug+6qu~wPfTT}Pu?;x_vonT>xfe}&sE+gEtK5iRr2la5R z)yx1E$aJXSKlCQ!LpsTg(9JhA8W5>w4Gz~s-`;X?cm~p(rAAf59FQlW4Jt8>CwL`I z<>*^yxgjckCnhH+@+K&qRdBEz8P|%{P}zGieKSq)@pCdoq|PX*2L%B;`jB5>1Oi4|ek7?a0t zNF+p0{1!o@i-enQNElX+FR`LFG{!f;#pqI3xi#7-kdWo9 zi8yvmqAa_?Lg^!sY$D|JHV|7v)?R!!d>JQ|ETTHV(Ud&BM);Tjzr9dHXS&N(W9Lno zd|`0O#D}iBF>_9z-JJKJcd6I9loRU;QW7$K|1Dv67xvzLJ9%A;hq67tPdnb9zTGLX zr$On;{5y9_?v#|=nO;+E7w{{|{`$Vn!ad^+zxD~*5|y=G=xu&x`L3l8=f%ymy}QoX zqS&|Jl1*;k3{%%_?9;tP%UKt!4<^5^|DhVQCU(W~HQnOdZ)IN}I<))k#iw1=SK8FI z-PR@l?7C$m#&;8rDac4hwASxlezEfCbWo9cu7|67#p0FjTQcj$#k{@O zH)2+TKs9)Ui?rQb|FpFy_pf=C9Ju*IwBL;QV2Mgp*Eagzv*H%L?)Pnk)`cxAzFz%h z)>B;=9{$xgIW}c_t{t=Im*z|D(&SYZL$0;1dvUhC=A7uS`K;ftWx;b7A3n0#RWo|t z!O@8VFNf^HRw<14x;ewvc9>bXM=<}yo20MeI^)IRFG3w}PhHdQy6M*5Rwy?k ztF2`=_54xg_;%{5@NUlsR;Oi;(H?ws;LAQSF_C!#M3W@S9Tjpb_g*i_`rF&T)OF5s2tJ{{ z_Db}Xw6r;|Pnf^^v7xQ@s6y*av72npW_wmgj@s<;d$su9c%zB0dC9pNCgWzjU)0WE z=;kSVy1jl>qLz_Td`k0Conqqtj0KK0T5H<)hm}9xr8vj)O*);`tMj?I z1DIjs4J2#mt^udK2DkGgv6bKvFU&ghV(M8~LQtM@0d$M6prl(#_S^;XEe`iQZjrqDO*`bi8?c^|n9 z1(b0$5t6nG3hyJgKsy8A7W1K%?{lGsYR7Sl>H~KOB0CB9 zPDw*WBiI(~P)7;9wq+r^o~53GcT`9cCL~7abzj*1=(Lg4Q`rqAhO%b|w3W1{*94Hw z+oG~Q;8#~?i4+wD!mx^tk|&gD!xJhZ;Dd5LiEDXpeG1HLwZP}g!B-82HwqYY+$JU2PzHl&n3WZVTZ~-DWW&_7a z=p$u#?nL15B%nX|xZPyQOgh;OOq`MW0I)F|a&%3Lhz zK!TTe2)>yj+a(g`SnHHH={-x$6yBjcm3=|2osCmGMle(E__tY+8ew;%Pv*Qcr7bOD zYIMG)U2UyD#~BqZ5+j#Iauo1*+}OhPds}Q=x&8Te_g80P$GwmFy!~hWdzUkO!wc=Z z&SDXceJ1ORja^yTC&hde&WT2Z6~Uiu8KWiu_&$Pox}Td z+0ZW)>K+TF;fkWiFCH(*U9hYDq=i+^ylL`zy?oZC78IUecOw1#^Yb$%hs1AFU0irP z$8q_9q^+tCBcG|3Z5*}vs6(arNR~z6=H3pbsS#bw1|=ODJ$BZXh-2gDTWjB)b9R60 z3*TB+8TQ$>X8(!-y`7x)ZOY7bJRCcAUPR9UgO2uc4_4e>w<>bh!lcQ!16v&$71*Zj z7c<+cu*jNg<7G#~EP@9{+h^-2KIz#fCw+_0Ik9oLV%G96JGyQ-xvzNa*!a`|b8gy8 zGB&?EZi+UI+`Z@5xW|RAPv$ZvFMk~DT=%VZ<0ij0U)?8~kBeMWKF)T)jKPI6@m!1c zRk8h-sP>JH7~wi*WSe&B(^m|9rlG$t=S5Fmi`%nWwLNTDyM6G0#QN>&ljaB4?cSe$ zz<(0^&dg@m=7l#}^tf&LaK@5JBavcFZ{3rL9XCxD&G;2^D*5)9rM{JKwy}>k--yrr z#&=ukmy$SZtIdzy!+UPH`jP3E5I>`~OOaNO?dLLAsmJD?FW&W0x7vGW$?VP}Z!Wm{ z_Eh@j4>{=p3p_QgJC)d$kJR#?ygF!J_Ik&+YyJ?`$XLBrouend9qwRTS+L!ra)$Q>(b2<2dt01Ku;0S(RU|og|LlWa z6P#_e$Mi0>yVG`LS;-vx`)@aUdtYt{HXEld3{4^(vSmc`Q4_N z9ILKLTBx)7%T2$vd4@5sC23)gTIGqf&c;QKO5XXj-|Ey|!R}FU!wpJjUgE^Xo z_g|oy+urbFen$Ng?1~%m+s7^3C_CS`ZCUK%`SNRh!@cx3Uwv>s!gS?? z>w4DqzR$OK4bJ<*f3%>ede0k`>$Z9A{eA8hE*P+^P0Wn<>vw4$d0{_(<}$Z_plY=c&ByeS8n0=qlwj5 zdd^9iHM;YgtjfXDw|713P%+h^T)X1fPwyDx?*kow#r{^Y=zFYQ1Mh8mKQE~3CsI*q ztNBM=W#LEEhIAW2PB$b0YYc)%xBRt|Bw9_#35p4vZV>I zgCVIL?=e`PjgpS^D=Em9&Jq!ui6jxE?2*%MgDeSh9U^DgupN@Nq=+;eh4s!yQu0ShWu1>oZqP~u zhpd4_^F~!s_%(?Tt z3r9dJLV&PlfcgICEn$rU;Y*wTz}h)uokk|1h5 z*|rfLQKRIx===|=B2wZlNAG`1z7QTnGoO0NVY*$RFNxMkoY1>Sc*g9_lhQXT>2?tK zc3`c9(wQ{;$Vnc{VsFT#2Gq&qmr2+J3r#7V&L);e#sEurre{ZG5z&=8QtoAJvQr}5 zw&_c`7hlOKnpgucRdm)!T1&JhD-ht0>~CY~HzJw(xWBcOd(|7>ZU`f^^sR1CDcKEJ z9GU}odBCqOE>8a z0%PHQCoeJ$EIjO*ORowO$4G}F@77WotJ+oC@P-_|Vj^bc_mLW-F0G|fw!E9vlX7mZ zlPcOURjx}FTi8>|5L4OJeWV3{sEB}53-&;;bTHYdvY_62q5umDoTA2(9VLi$VmD8a z`q6kHlhA2WgJuokCjliQm;On8Xh^! zxmD)y%rt!Ky~HW}@o!_F-1oOCUNP9rY{9BoU1CrF`1!5EJ#V-{^w7l~zLz7~`h5?U zCLQimc;EH=^dIf&O-JtY8b0xu%G7S%1KiiWEKNqsbkq1-GLvdMw&-0{_+?t;2m3ew zwla@C=`m_;=#jyf-=2NeCHC_V!G}qnrwW#f)-G?G?5`f|k}pCR7xY@=UO6mpvvZYi zTHnE)BR2#@neNMr(|1_WUv*QxHMoxvdw1BxWSw)zTAA(WV4xVPJ0{Ry zzIfhc#&6p2LRa-(qjqo64=+Cbd6FknQdqn5deDnm1#NBrwYo7TbA4jj4XatP_fu3R zhrN32D*vo{Vo1vAr#*bJz*k{~u=YYLe1|<$%pHsW%c}b;auQ>}d@5g!A zDne7{2?l07ko7w5fAHz@UNdcNlQ#$!ex0&x`RIcmN56`(tX?M6Ng7^J>TA$)DKlfx zlKKy&aYa3%5~oLZzRB3_urj?pCA-Y@`m)%p;eC?T+l@`S{xkSnO6%?Je21fT!5GPiv($Os#Wx#Jq3Cx8E-r*q|vn^D8 z&r5sM_Q3LKp4+y*UV9;DNMF@}J(Xvsx0&vlaC=Es;J|SUcK_)3JIUAPlubeJk_)`x zeT7edjsF@Ouz%a~$8CMj*AK2a7i(7%|1c|Z{@m3&Z@=$YR5^R}jro&}vL|mneCPhL z2R1WKd&g`LpAK+m(~gkf%tRp)lwF7#&}_T96k%ap){VYbUFzT7(-|JdGKGyCWI zJH^JQ#y;QKc6i^c`1O+ZOQ$GiU$35esLrfN!YRZ>EcSg3N>QKOej?I7D~B?r5dr)zJwe6RFe$OoF$i_ezV{T`cSO&C|Rxe zVQ~Xa8*Pu1-u??rhKSIi#nM*<2Zv)v1gykp;%s>IUc5x=PjflEGuFUL%=TR^t)eL- z@ie6;eB>y4t(0DzH}H|LJJw0L*Ko;IO@h?0>BmWnlcYV6>j9~VbxM%ZA8)b%yF`F380J~1hH8tK4eVYf)aojDe5Dv$@Aexd2qILEZs4I^xo)Qv|P$+{VNTpFt-{h)dQ8!my6ibXQf`0NrQss zY_AJa9|B|7T#}w7pYTQvbZE)0y&}!z48qQZd6cuWN~G>oRXo)wJO(x!&r79`Xqh1Q zO%M?5y9z1y&M|rJ@8d4BK(RuGL=AR@7klKT)Q@I=P{xaGS1J8QzOtb7af*OXUZ^-0 z-sI@?NqVA*7i*rwzAE17@5Oc3Dm9f-Z{6Ykg%EZIDCU=GGGLedZVhmt)WU&FR`_8Q4jKv#{CU9;% z+_S-3r{<0E1Ia30u8JHs$c@;Pbut%$HeEpIjL-Z>=BJD?#9WgX05gt_S zOaNW1Hp=q30Y0!OYZBlf;)rDQ!We$3OtjYAA(Nu;oiZ`oakH%Ha0q%N%1zmA+hn~s z6@Uc`#K!b%!;(gLJPYGrQc2j6=ur}c(ehN;2I_x&n@eQ9&y~T|?LN*GkZRkrsrzKh z=(!3%Uu>d!rvMg{vt`5SYJ>Q5L}YlsjWC6pCuH1jqm)Lm15U}NQa$kI8gpY|&@Q4f z3-Z*6C>H$pjFL4>FM`NWWl!Ib9il4Z-U~z-$XF7zAz3bF_uOn)3}oq*-;$9#dPI_@ zD}qRzbX&HtX}(Yff)jy5sca7s#TMV=A_iY&6W)V2!v_2EL)rL7*EzCbWwKKG6*ur9 zWRXuHq2PrKhe84vvIX8my7^Rgfa;2)0o~XF!t!3oR?@+ZK{;(J9Ksg9=3EYk8c14r z=~!%ql2H6wrbc5LUp*7vgkJBXj62HW1n<2KUL$a-llhXVU?IW3By|LSl{J`z9sCx{ z`B*vA^n>J)fBy=r+yCTe$op)!bX5Kw@>?ff?R40Y{PnPf{@w`v6fnLGOMUTn2h_qq4QO`HABLgPxl3N_NV~S`YB+oTKRvW?_GJi`HK8UeG-YGB}v)eEaIF%X^ zX&AA)EyJhG!S^ju{s8zT0M|l~QXv!f?8+F^ zGKAY>&ng_#EhXPK!V^*n6^(YMdm{}Igr);(Ldwi*J&GvGA5iajhfM#u)Gjv z3dzbW5Ob|D=ssdJW6^YB^-Pt;{DqiYnlYe*m52kh@HGVHpI(T-@Q1u{D* z*Fm{+neIeR-$jfonz|5vLbBl?Y;Z&8G1CaVcpgK4EwL{K2s94DJoQP5(BzQ%9vA~!Ke zgT9ZE)26rt1@QQ~DveDp(W%9ZiL{(UBCTZ%ebe*WPR0bfz-TRD>SP^uX`95Y_VNw!}G z(3KQQdQ8Ru#u7_z-fzxRiD^|ca%f4ZLmb)ING^FfGnxooJVS26A8VjkY+#7CFX!Z8 zNAc4P6k3L+PJj58q`d|+ER-aE#sjPnvqMh(<+B2~^e`mAMJpI5BCji!7j1%S=EIMd z%T{m#t1;=nCe&>B#gm#M%v6Lrt>n=DD*rW z*E=h*x?rP7b#_PX*D(6Tr9o?e#S}XNOj*O|5}>Ow_&LNL+e3QfC9mZKYODq7V~q%@ z;+MTXn%00_gt0#y8iutZyectptP=pcG{wC1R%YlH&~GukwBKGyzZGcuI;JNXd1@Vu zT;@ttW7c!35m$e0qe$%fx2vDr2?&V+j#cnPLD}j6cNky~UEp-lwe_4W5AmeAb;g`< zFFq?Gvuiv6=X3?2584^eQ69lSeh5UocLUV9T5L&D|^ z2DS}Fi<21#MRcet`udCGW0eS)v`B)%z3pgjSdF2f6Dbs0`%x~H7I%n!XkeouvoSc14&yy-A!C*IBo*O z?oEVx`XpX$B473jcy_>0Ch>SP3U0T|1d;l5&X4Qiq3W@E}8M7 zYCsP~zUMxOYfIc5kcJN=FcW^wKLiM^0rkjY9KPYO;wqS?@>f{)I?zBVL(npi?4QvS z%ws_Y!9c@6J0dNrC#*=7EXCrYmT2{6F7n=H&xz{@cW68l6_svEdJf7$iCY*q^s}{4 zir#JJOqOi{&cAapsugZ5XhW=3-~LauI&>ZF2d@eHC~?U~30s(c#D=zpJ$8(O1-kW-Le^`d2DDau<`fSNy&!Mm5{GQ2dRVEPOz`2Ym`1i8!~*06g`G0RP#} zXcJ)Wb^xkBCctmoIX|Di1VE>xU|03Te z3(bf3%!%eDAcMa}Oi{&RK$*YfjB?lmsMRuxf)JaCD-?}-GwlLArFonuy zyE#$*(DRX+edVY3_JhFo>0hkVT2t202vY5@U@TT0+kFx^wg99kYm|k;_i?#0Wgl2H z&y7mVa03l-wIb=_=Huicvteoff;?smqMQDUL|Xe9dU-*LzN064n#tIrHTyX~ZP^c8 z7je*{18~SK^5j8bhC34CtXGOC<9T3s47@z)*2EV+-S0wK8F@pCz@ci~M%-|-d*Lvc z^lz}ic2T0wM2r7nx)7$7nQTj4ur&~L?xI9AHh?nS9{@e`iW@?p^#M@wWnYSlI=~o{ zm|2gZZGtEiRBuV3WZQKvm_o~k@+5z4G=mSqIJ5qgq8Mfwcd(>ke+r|*1~o%L6H7UI zlg@?0%fsTSmEetW%%6&<3~iKXf?|OwQ6~Nn@cS~dkssa7{lL(k5sgq)5%zyWYrMP!x38q?NVqS4I9P$RuSTwTBWH z4V0SB3?ybm?1jqyd?g<0{P%ok0AKG_1c{*CrVANz9Ya5^p-^bEX@WG5aT5FN`c^0VxH$1C)p?w^Du!v$%8E;Vh66 zw2eYHZ)>iA#Zf@b+>TM62wHXOBC}pxqTGg2U3Sq?(6&<#eQ!`(mJO&isT2ij>*28n@sqO`P+N zafhwMW58wXIm%_nIVcs4_24Z=y}LkBP(_5O<#Emfp2q=|a0T-sWloHsL#PxvOy=_3 ziL-VNvtZKlA;6U@x@A!_e#OTbU83-`Wj!4?g1d6zubqmSIzMK%=r@B%2W$YILXai`zgUcN5lFIf~?h6 z$q^~9>Bw?hK7;-}$>o*@nCaLmWu|hJQN>ej3T2;DKonKoNTiF_pJH@~xt2czZ&<@5 zg+NyEQIhwtrjcHPpiQa;dauf@8m)hU-=vR?`lu!U1V@YnC<*Tc7@hv95v_y1egYd! zU^78vI^^x`_ZDQ1hQg+N(w_2_lbOriX!XejR=GbKSz$@4XlE|x@*^0nFGO1BV7G*4 zn$8P!S5<2&cba)X)Kx@@z?D*u^AClFGH9*fc0IKtcNz`gejpvlg9(>Jn4B}~xasa*N)-nqlBt}v7Cdhn;j{|$;b&G-^`D9&!U`xclE#e_;k zYxNtMsv+p%s*2_>;T9PCtjF{#;6pvg}n@w(JZz&BCIQ73l{{Hwu070oBf$qQF0N_G7L2Qk0V4uof`QLEASK%@?V zqI`vO8)=^7PLCtcfv74sJl+Fkp5v|-iUBH+c<@nT5OivTR(onF+E}h?g)jLcdu4pK zf*Bn8SN#uf7y!*_wb1n)jD`#kART1U^zCvz-V>C1p1YAEcYrw^5$`fA0o)1puVcy+ zChC+O=-vv1DPO7bq0?)_Fa&*O&5_0%ZUR#-fIUf_o8h77tYs5k^&;TSyqe*m0j*in zF#{i3d52KqMXux(V|s~iBfTT3V`M(Id|J8sDEQX~bnM$S8SrUcw?=wf^tK!Hfz7zY z>4*6XPIf1rbwJ`m?m%o*2w?pl92h#+%F*OP?lg?M(vs7#;7G3~0x9~{3lb{6Qzf&F z6#z$(|CFzT*C$_QT9XjQXLsHs_-c)Nz>5AVFX6jk|K#eAmAAqaGIZrKr}uq)>|fO! z1+8r@h#zkJ>@-9Qd7u^oLV2Ec1yWXVSGcs8b_KX-^=}lQjecI?1jw%fYDN>v85KU` z0(tZ3dVvAhwhOXdAI?PjPa3?5z=ROne&g|8LmOG!;bys{=LU+t2B_$eW~j%|O@`?1 zVR>f(j<5Q^So?)GqaSjeV>RVEuzDNb3=9M&WcKmmyd45>m%;Qv=u-R4iy6;9h zC~hO)8MQ9qO!q1Q`7e_j`Pre=63)xLc~zlTK=yAaCj*uA|GKpqef}5dQD=RVyMpX^ z6Zm!C-3$*6DCw-}?0+8g2xhDt&R17BS2h*!W^IO2G{~?M>IK;O=3vO{* zufeF!N1BZy?Sk(Co_N9dQHe_-cwiOWmG~)%+WOZZ++!H^y3K{k$lJjAUiP1yal>H* z4Tq)Z-`kuHS1@I-<9|{Tb`Ezqc0PB2=)uW9iJ%cNt!{@gUbpa1JYga)<(OEN0@1eX z|3O4A2&3X1b88@-iNLwa)=n$_a}YEtHbV7qJ4K>T?JjV>bMsFkA|9F+(|Ie~(DL*h z$P`i1KiQAC^PeN1!AQdX8)pB{ePtqgjGlB4P}L8VQMBPR>iZD3RydCkCQmVCz~etD zVMe$O!hOI$fAuFGY|ui7?{g=Jd`$H0XEP#)UwpH_idT_+-LQD@q7*{ZTPbcf@lov~ z#ty{|m9LQ;F* z^41m@yae`>K1vM6pqfVveK(1m#wY9Hp?0`OvIMPr%+18M$H4Vc6G{(3J7$xnjk;MG z;I|nx4s|B;mw=&njT)iQz1vtIN0spZMMTW%Twd|}I+*naH1k)|if7tv(C{bR86^4% z@atjTh$2m#X~u2io7f>6SltDWD&LwUB3?OnYeMdr(j1Ug1>^MBW!^Pprd|mg@KRFt zu^1JUbMMg@KLriOI%5vLDEcWE>>Dt&n=6IB11Ryi`=R5O)u4_7P9Mq)A0o>Nj$w2K z4E6HBL%X886q9|(jjBz0DI?T_%ZVs+W`-|bZ-c)wMGj8GDAKTse02mYe zSHKJoq@*6tIpzC22U#4@4baZ#+^iqLsM02s3A$q}*C56Y3lYP zw-*d;q(@#GNJJ?wxZT9H7qFdqh`*a5(M!gb1PM7rhYUlhFJNCe_9ds)!j~XAje{P0 z$z2lSZ7yFw1ncUE7}#$SzpOLJGBO57r$O3RzTlchNL|o`LT+nIdPCE#?JJb|ird=a zHy8N1p*XIr5NW*H{{now>NM~{kh`SY>F?~kFgu7j_~LZVbI_*0sVMU}4^ zS7J1Y#G{doY*ihVPZT`^pZC7!4mMeUO~Fe3RUJ)7L5pSwRQ86UkL~IQ6$ebyR#9Q# zVAn&5{xs5h%M2ur>iF!_mibWDZ^2cVa*j)hZdAn*jrhUs;|`r>t%rbBOTa4SZOBSg z`<59&ObM!lIT^N$j<2zR|GNTcU`_M$LxNh}$XXw{R5hgTDj0QfeIrVaGA}{*b!cNv zZ@&J;_@E?;ht}Gy$YUAmyJ|pHZRAjQptT9~vo;_GZk^Tc%X~<2mB#Q$Pst0#h|+`C zT>nbPOSCq<8V%0g1_A!XxS;y}4%F;; zfR2ZtQb#KfKs)fAwVZQ<*RBlj4U_TdugUO3i{CSW#Gu-B&pYb?fsc!dim#GpzjHW) zY8V$nsx>3=^HeaR28>WnH%rb_(zqI?Eg^kf@Z6#d(wrwecj%_XbUm1LGImZ{(_lsy=c% zdG$I#^(&(&m_K{zina0;hmRM&bAl_ant$1b8k)qq!L zuR}=p*N1CCC0);|Al?tg2|-76Gqn0E_ije&S741vFb zZP6Crt5w6!O}P{qpv3P?5UE!gHlL@A1Ag6rXqb{#naMwx@iejEH-C28VD_zW>upV9 z&z3gnhZex>3E>R}AEl9BIa1u09GU*)GMC#=u;-~mBNriZa)cLD@r4KBH)U?T&l%w3 z0OeVEo&M9h*`S)AT+uZB1^gbmD5LZd^fA{)>l@yC*^Y^}!Yv7>x)8ni#hv!==$4tP zf#o58C94&1OVUUWUC!wSa&kSMj~>lX(BhoFh*5@7pL#Vz`S?MH;*s^-5)Aka_(T0D z9=zg2+SHMLDcN)IjA>5HHEeM%l&Zi(dor>!diukl#_jmSFLWtD_j zA|i>b6bTZ$N(gSOjl@!0#h!>g^hyXJf=pxAma3*ob+J>uZ7Ef1xr9<$YO7j`aEt%% zotg8UGtU1!&v|k(?=tUf?`-e)`#OjgoF2S`^+UZ=E9yr{yNIa;cdvwH=WrA$a9pIFg`^q_l4&tld&s^DI2+2c$Df^1^=-f3NrN=tK;#2jx zWEHsWfQOfRrGb-Hl#wkWu&^%8*`aO zo{$y+rQVz0OH#%Dt#^}N`=E^`89n?XpJ%(XWMO-O-OUrv!1OVg^kwYw(=w~Gr{Uh8 zx~tLq=9zaV?t!Uuu|Uzgf+)q@pjycgo< zx10y2d{UX3L;^iQf4|BQb>MG$GP%6>{cQJaGI+gJsrUthzrLu}p|$JYI11KquhhpdJcR<*#70lQ^UuZ= zGgUR9`@PI}BVu1$3pap763G4iyj5Ek)oFY!0NK*jo*v3G%95Xu{yC8TJ^8VvN^(Ub zw}MI-eG%VQ_?ka=Z&~CdB=If)InEP((5N+Vdu&(&x^$fDi4==FV}peE1NigjKB~XC z_=3Iwcf>@;djez!6H$#N_hl-*C4hhIs|0qFz!gO@y(++!7euH6T+N%>cO$GmjIZ7` z9C6c3xd>sO0McUIV^dzHR};v6_faAp zeQJ=z+X-r!MaAjE^Jg^U)*+C1H*r}ge1avSyzV&q{?Fj|5_g4y-x4@p@4_B041Qcv zn4TmMk7-jykGn6(ieTHFqg^d`}U{9az`DGN3mo1i2)2kg1s=YL=Tpsb_o0RKVWL zb?X&Mo!5s_V|degY9%fr#-9z6yaL^RK|NJ-=~XG+Q*k^o6)QP?(Dlr+;1_84tFr9y zGWz;Tveth_G%H0@9YzWCyC#RXUzG%ozMM*P>chSolPu@}(64Pi-@ZunLAeG!#7s(U z0MfK62FZ>f`hCpy%nJYP(vT*2+-mZxlT6q=DFmo}FTNH}^oSAlUF#-W?|CC!56cO? z1#W*T`GLUiY}IE(2W<8&_iTg?nAuNNMp!{e^cr&UXj0wR6)9HW^n3DKbrk=htl`7j zfFH;buk5Ag9Tuty#GeDi--pi%Q!;lVdNpkd{xUR|K8wa3C7B3b6{@;m*_ejIE5J6$ z|5esQ+@ld|FDl^26-Nv2Vw;|Yrq{>YzKc@d+D6JXf0nj5$R1;BJ$4BC_;S2qGS`Ve zH?eR0W^AuSl&%Wa>gSm`_e|{0^cDf>%}c`2vJD=ZC{8@*p&g#vHZ_#Xx^#~mXzL7Z zx}6=K3mKl!SWSQFjai_r+iUhbC6q<+RgIavB%}Us8;FRel!tb~@-+c8L5rx3JsUoH z{2shcFm_fQwb44!LLgks%VN+^4M!eDct?gwnRp|Ck5G2KaBJOPq0ATKLRVG@3}TZ2 z19(4^XG`?Rh3$DP+XPL#qF`MUHSuh10$NiSOWK1bOg?N75&>E#cQehO<5dN6@)cGt zAqE>yGeaG`lSi%mXim5FhF2Du;*|xCyp?IT>lz8Ukrp&B9#~tsk&lTsEyOJ6$Dc-6U*8any%a|KXqt3ipk+Y}B))IbR-*(*81n{;5`8b}CvILyhr>YZ?mf1kciJ!=Gws=Ah zPj$-p*z{;7fSkq$!A-8#;94VEJp42s$y7s!4ne8I*T8pW0lk= zu}nUa_BfWw;Ny02O3wn*j}vi!a92~EBHpeT?cjHy2(Fw}1aS4itLW}sx1SW%C z$dmElW-LzF(W@C7B7V?85dXQElJ#oNaz%1+b0#0m{iivbC{iZ8&ho^MTd%XX#1C5U z@SGN`mH2N<3zjc_gtla3#E<1I*(CA9xs?)qrxhC{Qhsm6CW{{wYQysqSqt&s_CzHb zlEg-cl;R|n{mUdaR-}w?%|?kI7h5Y*&o+v5s0|w|NJ48$TcveITP9B;|7gpG3p#lS z{&q5xkM(X$R%P`|QS^coRSnfC%AgVL*i<3A@56wzUhCck>e(I8dPR2D5Ob z+_Ni4?m!DUlO|2O4;fOZhiYzb#$3GJ*qoBMSgq)!uA z(g2BeGq2xiuVV89-~NUgp5poA;Cg&}Z=n4VY_ip&z2w)jVme`!=w!|8wKmwVTC)?l7Ips%j zSPQY(>8u&#$m?}yeMI9<>kj!jEe&}yqVBXmR5X>1q=#F1Pn|m!!*M9UQG1uX$*f&66-#=;^a$ zO`~rQL(Gp~NLN$wy>uA&q?bW*=fJae=jlCFr_JgK`c~Y^Q+Gj*9vs-#K0GT^ZEw!4 zUu8o#)I+gZ(-$gt`2C(tUSszic%-^NI>t9>EBzY#{M&|RUqZr`=bcX)bo*x%3UC{% z#4cJP*71|QSYOc?5xrsAtUM_XOl*Hqt%5{R2N*-!db1Y7Ct8BH>WzDum?8-<8=(}Y zGH3q_n%^%X%~%Q-q0IF7oak}AKZyP(AC2__rO{6h_ZLfkK)=cpl!y#yS-D820{2y3 zNKCq1@m4h2=Q!#tMJrlSsbmEdY(tUPklj~T+(SN2s0rQQgf5q?-TSD1F}@G-n$2bE z&OS^Y<6fauKiqIrIbfH7iIrQyu6;qOSVyE#0*RIP7~3he#zvX?u&+`sYI6>c?WZQa zG5z47s_6en{&_z&psRPrRjkJzU?(1)&5qxUnt{%mTe+h-E~TsQ$w^>5bE#^t*WE> z(gCc$=qi5>KvqY8B+({3VIY&yS9z;kKD-2x{t(w);3YnOAnPSWX(?b6#jgxhlYQWu z_jOk$hIxg9&NYi35VxSuCbS$*RWgif)Tu5e}EAo7?=HE-6=C zV>6-j&r1eL?gM_I)XLwDR3`$dl~0vAEk68`?lJG;t9QN4cMX0!0nc$yj#h>xFR4w@XW~wv zLs%ox4cGtF-;I2HA~bb1S08?D;|GVZexip24u$BQ=Oo&gXAfmEz+lkny*tMt^Tw!= zWKHz`YrNhtmPnwx@=pE9va3UTWB%$0Kdeuv$9c#wHbCSua~N`2=|rN9IYID>`)JYo z1_egvla)1LrS76{yYrL7SvU{z04n3yVao4^rg|=SfobR9(sk2<`GmLB+0-vR^C~jH zG{&k$c`Y)cGY=cCE^^xphiM~xDXRp&WH^(T0`E-kap)whDuh+~VdY$YVz}D))E)uR zUA0MJD-L*Hd#g#Y^|R_DbPw{t%8nQHq#d_LC_8LMLRms6ADqJyto+hoB9=>{00!m& z2F40)r0RQ@iJloQgbC_by5at#ka{CZrUFh_#s;=nlj&Lso36p8?poU(i!l^<^Me2k z77f(i>->y^;A@Yn=%w-h9Z&qJeD7$*nzem?nmyJrwE|u}a!hDOtnCOMST5T-_ugZZ z4J*1?S+O-)S^+p=AA%#cxUNp~i+_!t3>%I9rccks?WKMKC>Gb-qT96`g9@nM$8xvD8DeR%~+VW{4K*YxlY_osXa$g*GS}mK6c|F<5Uwzj|1uY zXxVF)jZ?j53#IPNkpcih_A-Op)Q_|Cn1IS^h#F4T%ziXh@=qVH9NNSRIpAsC`JFQ5Gcvob-7*bT;f#KxpTD){ z1(VcagSgE1p3OJpvijwdRjs>EM$HV9iM(_+0Kf>4O0;VKm9J`~@@VyE8R%`TF%{^m-NG``(p*5)@;y^j z;Km@&ylRR>SO|mkX)(P*@&j7eUFa7z*fk$n-&#eaR(yTFs;lj(z4`ml@HQ4_`iB1+ zKbfx%qJpPF^xlUg+LkBHzTw z;p3)5mF>6E^rO?&_UTtjUGlw5MS;RZS<1T1OD{z^Y+##S7u|l48SI*&6da!c1?^Ar zIg`-3{?}Cy;{Bvse;PM`QXg85E@`5^cUYSo5@((ADw&o^#xNom~M4ztIn(jyqfM1N>MvO(lNR_|DaW^upE2kGtVXUD0&Gb3tz&U_l=n z;3OMC&=(LL?_xHa0=#6oQ+r0Fm-Q`3uQzZ4=pouqh~~Tl`pJeCbU-M}`WN&nqQ^G2 zpaV}CcVH&@uKxyoS)>IW;L4INdU)bKoBOmf4Mz3pjovNBLWm%kxyL-^BHHx%sKZ0F zi$4TAY3+l^nR)6ev|=7|UYsD65I!>pKC`_!`jp(EO%@+q`2U>HMW!We{sxK%!WHY| z4q;V=$_IT?hzu^b6&v0_)(S##C4B*hBpG+-=>f3;DG3ehEv$|pk=YDB7j9POKD!ZfBns_#N zp*s0_XXZ7}2no&oSIQS^il(p_G|ppAzk(Wl&;F;eTYF-{0S$3jJv`s+m~ z^>+Y!As=vOf+eYT2}lSK6(n>|FY&apsE{e&`X`j5_dG&Q`tqiNmzJpI%}J79R4CEQ5u_;yDTbD1BwhE-{v{F@XZeKYH+Dib%9jf0=^5S!qy-jV`}5JWgx|^HAs%UpiK3w%@;cTm5WYG_&a)c z=?>5rQgxwB#X^ZlprDPET8Ho7@8rN+FH@C~x(peNGn1V8(q(G3SxKa*9Trl6=9bRG zbva1Kzp@|!oLfdvO(0Uk-4>)B%b5g-32brn^axbRQdEgPwvX(!;IG~bAO2%&)wAr} zZVldslk>jVE9nc@^8JR8uQ}9U=!mU{79fN3R{eX^QQ1c>uTZBE{rc7%T8tW`FnoPc zzKDC4s~E{1gwK&D=@GWD-B(o_J6tA`OfoH;NAtfS!?n`o-f@GG3P6b>QyU6{J$ zf;1HnT=C3$3q6602YmaJNnc1Qx1PAdW`~V{eYl*g|0m zpIaF__nI^oK;$z1K)?&6mak;-V!)3+xuJFyd|H;ztC2J7!2>Mq8(0$<`lc=iMfejuC6 z$09^aY-uh@b{kaTYHvUWwy(=nK%2`G;{}xZB-uz6z~(Z1b-Z)^_bZU)94wIa_0;f= z7X01bQyI{Sc#RR=Bngn^c2*~G++oPAPf(D(a1MGw4w%KHmlhrK&bm2=`%s}#r8@ul zG$R{#4#;#@GXhEJ`!Hj5ZwvY`)7&v6*{hnur6~MKk2Oomko>6l+U8k8T)189D@g!X zcjA>FFo`?A8yB}VlKUAXA3|EUup(+B=wA#l=n^%rf>I|BHd1}~y^ZR^NNm@R;1SbJ ztf^JS-Tf+e%P~~iDJ+hXwLTq+$4fV{Bz|C%T4ty0kNYDAtaM-AS!2!O_cp1k%&s58 zj+$en9VoLafA>RW2L)f)e8*3GsGOh6X0V1#rhysB%Zr?A2_$L)Xy@ZMGv6A^^Can5 z9$o}s#EqVWl1LrYV76x3IQ?cZb)Kxs+`Iz9|~- z?~=D2-~5ppt4lvdo@=Ko6@ZGD4%w$o)tjjhU5KxKGMd8cRjP~2>6MVRovCDFW&#KE zVx`i1n`oO&G*pi(Z~h5uDBNr2Cuo;+e*Y7^fL_4ZFP!BZY!#(2z~lBpIzRaW-*)(# zKQBqNr|^(tLvvi{3Tm1T$)-nQ#NcV1A5CF+O*%gP0OeW<<59CXCj F{tqm7(lG!4 delta 45520 zcmafc2UJu|&oJ9}?^30AP!Xj_wV?tQup?Me6tUxD#jdDe7X>M+jEDsk6dR(T*MfrB zQ3UK20Snl%SMblwTozW|@BE&_flV?=CX-1rbMMCdvbI@eZ8iM+350rTQmIt!Xa525 z8Xf}tt8ZL{+YN&=M*`Y<_6_?YmUTRBonRXJVfcRUo#l@2-eeASnfG~ch4^6p@3H){ zS;t$hT32M{`8`Fe_1#&%9Ricq74d)lYnU1b)~S`=tsd+gkZn_<8F*O%(ta6UC=h>`Cq1MBjZmQznZ z?Ol+4Rr>1J@k`GawYR^t?a;Z7Cv&t5wj1w@?%i#k#@(nqJ*`(=(zISi&pk6?;>R;u zl~ePLC-3y{Cy~Bb?B?jeQ&a21S5s4&sp2Hh1y5D+jz^}9b_@|%D9ZU7ilKZnj6ml) z@i9P0v6}-}y%J(TJ2EWZ8UJmZJx~xM#(?anGFu73$aK1p-@SGE;NNKM0ih!rvrwv` zxS`XUV6IzG0Pi^S^d_O88L_1+@E_FLUqyQ>D#`hA0o%F+zo_^!iO z$Gti5z;Z+E)#B{$4>nEN{oZPUu=HkdQTrRO`t`LxD@vMO^jRaV@Zc*H@$}`FyzUcC5H5eq+0Nj$vTe(f79Ane!^ui%%Lv zYkGI|`NSN3#M{#H`N%heF7$TLH0-GR`PVg#-MkHR;@*x~67%ZD`x9ex6hm}-L=5s> zJBmMDaI@mpszWjR+qJS9KJ>Njv%k^Dhs7>zzB#}(#bc&wwdqv5N0h2!4 zMb5FYDZD8Nj>t9|GBVlq-J#gICoC?04IO^!+^7%xBON#OUHP*{;2GQL+bTEyylW-zGF5ngk6KKH^`NE0hP4>2 z|M=t3ee?UYNsP*vd(z3l$>d=FN&mg-De^qM%&2-JsyHdylv{`t!B479X9`D>D-dwg;bGv~ngF-Z!boEJQ}h6k$Sebq|fgv zgI;UBeZLPgHZqC+;5uyFXj9Mff??f1eafC$c`7lpL%S`*`*eR9a@Tv|h&5*mBZnTl zDv2>DxXQHapOChFO~R)6>HSy#d=;n_J$+rUqQZ0fujhk(`t>llIse@kgK*E~X3J)( zZz%g3UDDQNt$X)dIfyyf-6=&oEW+&f9`_FU=BqlMJm&9sZEgGON8V~qxLq*N{*}$_ zbD6g8R{KBSo7X*J;DGxG4J>?hB5>nXn?<#UerEJdURtv-Bc;z@jg-ZX(Fc>-b(&rv zIHl)$(dqEn(Y?H@)?0j>cQDQAf&SWwy>xoT6d(3_y5!H-`C+-Yy_OV|Prf;2sF}u^ z(&s84o$BFc7n9MJK~5E+Q9qV#5ZiWNR+CXWAVKqkfAqk; z1LkbIankm2@oTeozvM$B;tzal;o>*t@!S`WvO~xj z>bjy?R+7%h>#$H$vEEW{+_GkLtA(Ic_`izbaBHt{b`vU|SW?X#zeBjKXt*Y=97i>bckYpWrd0;{fJy>*5Ww4* zqjiV4@Lv8>Y>{2w#)hy{Se4?@B2==0G0P6LH=@wuRHwu!MvOe886!o$LkW?bdYdTP zGW(9>Ny0ok-Z_j)A#=#@ZGe_#3F#zC*LBQ}b2~s%1#h5du`{RBEoQI zXD~%djE16anH25Z$Y^EX?B+q%pnil2%G|_=vj_MLqF`*2?EKzuDBI;@8O+us`=x&v zr&}(q7=*mPhK}WKjsF_yz!e%sS7A|soH#v zGQ(?1<{jszi9T!w#U5@y-Osk7)BeYA%>ACmTxOoy1HriBjDK(Hb)>C-8V<{1fg zIUO_uF)}o*3$K1Lod_g5I^$;}?J=Rl7RDsI^l&Gt*{t^9ZIMTe(B1>o574>K;BApd zqU2btB7T#TVqmFOcHc8|sQm*r@hnhQ662PwbG|j5ef$8>0rP0Tm9fl@zr2K~rT{n3 z_9}Ft!-(hLP4uBalD)5ZE?rftQbe@Fw16=@dVdWa3u00KZNTl`!*^6sYa=ii=&$(p zxH&NhSq=>7`{XTAwNu$h{5w0bER{0xs2KY{;*=|iQyygJw@6k7rl1ji;}^i*U;OX> zSC~}N=rXFZ2N4W`$rhEjewlHie8I}v+H#=Ayfil;I%5luE_PksI_jKSbQ&j{CV!|-hRA3 z|BOXIr^&YS<4FjD1-gyOr5jErUEvJVaSG|^_4$K%pD7a@6NtiW%1|Cd&5Y$B z_$Wc)`@sNz0(l}bI|%BWV&(f8Gj>QY?+;-D+mD08Aayk20BDmsmS;n?#^Ifsgs{vz zz-X{KQ+VyASTP7-4M=r%eHgD%L{^ScXD#RPCebP32tyGc4l>&8_4zzEsx@{u7EJ$x z>V$m&F9bVqDUU}e6maICBkNZ}lieQ4)BMK}Xl5p3$o^cxv!ViVHlUnf*bb#51`@mK zjI1|b$q-5T6RS^i- zNs+}7MvJ|+tzH;#>cXzw$zNO90%CM!@M)QI(#uiSTV5eiutA%+ zQ=rZHcf96w0IVm`M#7qWxK4R`Q2uOm-k@Pz4MlL8FBH4=O5}p!YU>F&i!7b3qvw1$a&r zfr`(-1hl36XPi<>J7d|%>ntp37lzNODxik|tIoP>^2bsw>NO(>FcZ0UAoB5e*1YofE$tE5$Gj^aWzkn#Vc^c!0N-lue{Mu9Q?DY10TBsmr0Y#dkF2Mr*CTE2#*LLKr;Lv)9 z^mbaZA}Ot*YFKcteCBMZ80P<5HQ-Z2Kcb+P2S0`=$U+UYj{F7Xjy*fHD}M|%5Q!9U z(m`I``MSzm9x@$(vH%@UXIK#id7mS+L9cr7A7TlLPGug6K71?kcOv;)$d2{l52H3a z)rzTQ5Lxu0ri5b00s+Jn&F3Iwg86;; zugC;po7p4yq=}LW!J~ir@wqJtIXal|4A{jVh9a(kcRU`%f5@#hz*wze{Em%2rUdez zHGD+n7X%XaSrC6_1B^Wo%%@p=bFt7FS=I7otn*m@3rb;qh2IImM-4Ra2BXV{P2iVN zzXb)lvztQs+Wy0wICy&)JQ+1$%Q+I{zE7Cp%_8|0QLD zE1JYx{N@dZMVR6s_U9q~tNOwMa#9Yzn{xhx*gePjo*(C>PwO+p6>wtRf&C(ZI(L)O|GsT*QSEf_^%xYLqv9{S-osne8O1|l|O|dHq}D#i+U+3IEfZs}fwi z0X0UXzaWG-*fW1Y5mof#V#Wj&4HgV&68dvoT@~Fd`=I$l1X{+Cof5V#fjA%8B-X=; zuoqFA9MbbEWvG0FK#25TGRCaKP(dde=5gr+`HUdts^t>~voOY+=tz>4e~_RJ;R$)F zJz8JI$k{VN0(Yu0$yh+4f!YQOS~O9+X~qgtM)>i;HJkr;#v(;w++~4r;uy|Bh+ks} z8a75?O|&7Qkbr(yGcDOWV+2*y>cq(aVUB){=XRL!1i@jdFIjAYjC>)`XH6#yVw+)h zaPF~3k(mN*G{#gYN7r9NF(9eGhw}%`U+*%7E011pEFygqBZff&ijD ziFjn(9 zf}PY{uoyw?*%ASFCRbSz2E~4Y_!fIhKyTY1b#_L?<_GQwK9Y6kxz}p~D`>#h+!btY z5J*)7gc$w#4AXi1K(Lfpwtkwr?A9{D6sky5$qYNRQt*zlQ%;Npc8dQYg6Jw^CsYfZ zX?+Wcf~=7(`@Tldg36%lRs%~m@Qc8N!c=E~taw6(z5HDePi3esBfIaHU>6;xR0!dT zBd9Cd@v9LGQqJE*RQ0)(J({VE41-b&@rVl75r3e;GYeYRy38Uto`3t-! z%TP$~7g4>#nzP`}^ytQ(u@t_c{K$^8>Mb-!FI#YTcQ`L03}U@o3J*{ps<(v%<^N`c zX#F3?g6(B5{IAjUV1xy#X)FBNu+;S+eYBw#3h92%!V#o4AS>$JQmBo-*1}4E?J7*B zGohCeSeuYK@bpLBg|>~RO?C1W=8*tLwtdYXa5>N3!uj+vgDztYoRd=iGFGgnzitKmX-x+7_>$5E?fvQUCHhj7Lrj9_bU@PRzJ zoUIKJPNjK)WKR{Wh|L@)tR`~Uq{+hGq%J|Qk2+w+M(8kKuFLw)7LKNNhP>>I;`nk+ z1e`_etA)a7Ro5bNrD(lCZon3W3nj#!sD6ucTKyKt^;x?pVFZ;{zgY;@i2bb)aurB@ zq$3DJ^j-*N>69o;rl)izmetG<`qOoQha%%h%)M^mar~fgHBnBn(ZCUD2MC&>lmlR^ z8<|4Q2>mMqn3c}j-W5yzT*^JHTg^=^-=#P0bhTEfmb1%vSzwFtTW>zP*VbZ5Ze?#b zmsX$3&(1p6KfCov&60$bH&AJdyXs~Kpu*Lg3Pj&{Mwpjj~YDTQ>Db;44xA0>E-@n8Kl`2O;YW` z!$NQ3rKb-Idy+rIxU{p%655eZ*5uYLOQ?yQWpa`7=?20#@R*9g#i%$-_=C`s!)XJ2 zrc|znw2ul4FcO~YG#&#uO;j$GOORQ%u$YVx?PQg2|E#Q>jLnOuWCa$M+2y{~>0gGOL%s}sUq|ELF}Opwk=VUDV1gc*L8fwWEu z_mMGBXE>mcYeF#^^IlA^XvmiEFA?gYVogx0=V@U9U4C+(3vjmcS>X=4rHG|4EvtE6 z_?_<8(sgjHgh3t`g{3MWBev&dVLJ-K<|0&?SA^~9yztc_fgw{(*lsNgg&O}L*h$xf z^kR_(Yu=z(a;pHWT&e4lyIjl}vA2X(c#a@XxozquXXmfoCCnsKUUWVwcGu9OlmdN*w@DiZ~dlA}ZXuw+` zJ%f?qqxE1}=DibIP)p-SPIQ|LGmz_|J2iAC;6s1wL_?T&@kcJ2HV7&*ls7{TpM)=o zpax?R_%cJe7>)QW%)>}}CykW@oHYvlBD{dXENH?|Ly!jnWNkvfaiZbk-=AHKDV2yRUg49?GnpZ_WtSvie;%C(rN3 zfGb6QiC5PI+2(9b8y>o;)hL(I(qr4wyx!+cvb>eNq<8e4sW+DJLtnIdRT%DeDe$Jo z@O37I!_20fzOdlwq#pv4)ebtH$~u+qVjnp^4DR*#cWE^f@ps#RydEiON&YtmUtH*6 z=AD1v=gkne*W-uJ9M~|D|Q?Dm)z1F%|GyS>&O*a!)x>( zyV&Z2v_dj1(-qy9%u~CaUYFxSby3o5v|9;OM-6dWtzTb`P z*rE8UgWzUs>pZcar%rCClI4%aU2riz*WX^WOB%T#Z(EJo(BnNNZ~7IrS<}5^vwJe> zN72)oL8deE{!HC9C*Hcns(EhHe*TFqQIkEp-6O7h$C(#%nKqM#l-i*1zCE@^%repr zcpmQOW)=9~<`Fa8)pC}$3e;=&Htp2-m+1jdx6TM1rhEU(El>WE=9#DRNlb*W*UFsrY@Z!~4$@yDq3p*ji)PeNeXT^pbuNFT`=BM;E-k_hv!nF^htraGutI zyS{U#=$n7}dZOf8{AT`_=estZGpwzhS~@y9FE}Rq+VZUh$f?id0pi1seHVrwyTwjU zvDtBct)pd~7{Bc>e$UZgBjE)vM)J?w4k&d#+&R{JpyvO+c+7Ei#}|(V{Q6fa&eaL^ zjc>_6+D?VMIHO5!{7@(CL;N4YALJl2h(FW+a@!R`k0aV>20Pf(3^Ks=zrtapY$1#-PViJ8+v|K{-42hl?#QxF~ zbs+hKcP<_Ux8R&s4Nx~FIed5(=BB(#RWCX%BfE2yyS{YCV}0R&Nn zLoBltsUuHou+F_3 zKCu&7laPxd7xIb3;;btoWA@rb5xr9+X9^ZzTya{@ScJSXnMJsW}5`MQ&{EW05}nNZ0*}$bt>17PX?LBd2!|WQnHRf(PBN5e+4a%;GwnRJSZ} zxl9+nUQ^4>f#M>fd%fkgP`*SgM208xy2BIaQpc>fcms(E z`%)y{Pg9JN0Sk;<(3%Io1wp+%QcEmDX6l?Uay%o#2%K*Z3mB&^&ZkN%<*pIyCH_v8gNK`(a)2;mnO0(Y z-wl`S4RA|zrL~woU{7u%rjJl_=SoS8VUVMp*d(HTPIzE^A4{Cz9;VH^MH1YAY1i71 z+4?K*!`oGHlj4r=|D|^H!?N#RKR@A}e1d#(UtD5rY_Gx#qFegynQe^}&+pZ;U;52X z)>zm6zoMiKJjZt}M|9eI*$BAOlTbx+^d9<3O zUB1#LT!nU7kK8W?1uOWP*o!d2RWpc=cqpp!j3C%n% z2s~?#6@=xuh&=l2v|HgKEYRQJDF0wp6%^dMsOVmYaF5BaN3R(D=;FrWzwB3|F#$s* zS69#Tz22eha&+H_Q|iUqxr@B&v~}Vq_Zm64{KOE~cBAaoTFQ9nFYr-@3=Rr&~bg)PV?M?UV|cX%6eC{?REH0 z)%kCm%M5kf+l~yf2`>;&@9;XTTmJk!)d~7R~@xBj;G%I*nEf0 zymRo!&J&_8gW7fYnr^(nD_l!H(IU_~c1A!VMpn|>9Ed{7G%U#;f8cRWBET` z-EkkT`ikKSN3nr%057Fz8sx&6P4eLpM=_0qFC4|~$=<8EAU0t;wiTz5Y{5Qt7q_H; zi93tqY4A9^14wc_#h>WrD~}SJph9Q44tu+Ym_9W|vB^Nv-&fp12Qy`74;OQe04 zR&Rv(0gZ|^6U6ix<*kY0b2L1xTMIdJnX6o#JvUi=!ICHiF!II>0(uLi?godIIqSs# zkpV2opGmI*@KAQvCb1cb7Az>?Kr=FE?9_^A0@FJ}ULj!9O2w1uZ3uo@9BDgBbWqnv zTvUT3F_1`xq{!h)Jm9dL)=sX+etRV5-k>4Ln2fCXEEXc~GO;Zg1y{gMs2(IoGuq3| z(Zw<`ExjRP{)3dE5AC7QX3E7c$#8O^rovW^0z1IrwqJ$#51}BBUH+i}DKd1ogIpi& zspLeFP)o=_doA|%Gw}p!WO6aqFg~POEJgEQaFd6$#3=}H9i-j~B!7M(evKuw5YhWm z9&l^MnFi041zKH-_(wTFXrrkfa077j6 zrs2E}dblCyub?;6MQ(^n-it3_{YZIGPcBC$AH^D|?1T7kQ@hIHB?oaD&9`tF#J%hX zuQfDG3J$&B#agH)S|VWwd=+ztW0lpBv$5aA&FQ>>iw6rMPSM<_l};xET6jU-@adPB zyoIJRAt8GDTil!|j^AUdSDb*wsOul`bArQZtN{nMksvQ`$c(dU#idjj)B@CUn-@r| z$z+vocI<{kLKGy~P|0TV4GF{Q^Ci|)d8Nw&T#W8@lQ&131QN12aY5vU$x)6_Qcak{ zt4EwT%;m9&e~Dx>At(2WL^9b$J9d6J(`EKdf&Tu`QGKr z_iW3P2kcm_f6updr;V8jWA$eD(bt{rvhl3fk{B=F4zJVsj9 zQ>b?SiujKYqIO+aJZ$m;`EPC0^RFko4Ua1%mgl8vS(v3bPUoyQYeRb=6`JqC7b--rZVS=GQ z#^w}xT!`vnVdk7a0Hxhaxn~=_fBTS(C%UFv!<~l?>VCds{^>Ekfjc&N^qc>{@b(j} zbIY!5csQb}&^h>HfBjJrizc2KFB&yA@QGv2%j~m^=BtPsfjiaR#vU}tzPWr)&je91 zJNxv1ZKYWuuZDdvx=`FLphzxKOW*0z?BE*nIOm*&Qy$Lqv1@j3|NC1Wx#?cb!q(=D zA3cX}{cGXMOCqEm+|~WR7K;@NGpFpBzWa>le>cMaBz4@}deqt;`puvD4eRh_)c5bT zy-cD5U5jr&)YVxO|24wp$m9EedkRM7tk?LWHhJdxOW}``#}pYGd^=fc{q1G-T0{TP zp?$~x?$hoTI-~zXY;$tgz?%01uU7C}t_DBYy?t!$swY*8HHQa!RZS}KP-L&VeDY?j zyKV^Y((eH6AFq}zc=PMgn9h#_V*71f>hyM=IV<=3x&8O3W9xHVdcPR|-O1Y4{0}*z zu77B>YX#(FL)pJM`MbJg;{Qm(uESK5Fn-U!^)A)dVxCWDq+yVc!4p*9@<(?yB;Cla zG0~LxkU!wmYJnm(CHCYKdAr0)OJazgX>x~3defvO$tMJ)RJYQT7$IYANh+7ZWLR0&|h2nTK$xWg!RO5~)-&~@lSWrd}$+#?M0n(WUHz<9^ zG%<6Qs|%@h1{i-*8$AL)uf^X@Q2D|HB5%Y(+%Z6MYtB^U`VK(Ws9zbE?;vhFp;`Um zE-@c%6jXdoo+UUms7^~+hsiPz6bBi@Q#9h*Xo-iShQ!ePK z9}qw5Bza0%)z>Bl$a5f+B^q9mZ}dD}A8rj;k6w}?w0y%{s0{MR8fImLoF+o3SEhY( zfzSZ=9t>0aFhe3`XAG1ml*dM=#QtOZ|0LVA#`F9kT3WN3Z>+`8(Gl2lR>a;C)G zS&*7TbRw_xHWesgou^3_(Ut!nj9Jhri5>^Jv}nNQ&6kwY5)fYHgxmB@60?RI7h+Qa zYPCY*7;!U_-#lcccceJu8-G*xlR2T057iuVhT2{=TC*mpquaU4L&0wpv7SF(`uxoBFu{i0MJqeZTc*~~=ILFtKCmx4~bSRS!%oni37Pw!1WWXeyxyJS9X zVfp!1J5OIb{e9tg&zX0B-had|)2YdfY~4cDo0OrJ8Gv9W2V zQ@C+}^9jZJv4#5&n&!0n-TmQ^gj8pZ&#%@c_}jQ0x~4wW=9Z#j%W+-Td2KFvSw9LX z9OB+?(~w=(ZPHggA008GU5^dfTlXh)-5RweZPKdgyLAUo*u88}fc~8Isp)^6EAl;7 zP93&EQF|$CSK+l++cORx?pvC6eL>pAwCAVN>~m*oox1M0y1iStaQjh3--ip+=d#bY z9DLi%{GC^o@0!(hy7!%*4m`iSICjrL--L6?!{i;46+$oDq=YlwH@bEFeKKO~+GZg& zhO5F#H_f|x`^2R-gJQY{CJah66~)E<^$zuO8MnwWBC0MobL!$gUz+d9%#clGZzxCUXRIoyrnQEYjty%{U54Fz!a*kDixxe70R&w=Tjj_xh|>ANCoKM&C|c^O>6(lpo%_+5Kjz zHIq!Wu8eJ-a9=02`}i%3-eOgPyFa+md+@!4 zJ|ncA*WQnai_kN?n(3LmNxjFOoR8+7GWES14w%_w>BSCj?Uy{{%-lu29;N5}=RdpI zp<2NUt)wg)<7=OK-13Oaxt~7L{O`Hnt1J0uGi_qm&$f!2_vUqQ-9m>xeJ>^F1|uPK55WX>Bn4=-JuT6W2#5;Xbg|FhD4c~d~<3yj6u}hwRN$B%%iN*}45!-5Yvi2{UYCP`1rr5hn z&c-^#ZvIq$rCH!2+v-&NQ4*uz6)WG5`l8!yL08G&t|><>-u%=*wZ_FRiuYgmlnF62 zmTCT)A)A!HV&%YcspslBEd=)>bZ^&&IxgBg(sI=8<2=rO*#-}I%p85P($mDKAnnbh9~ND|l#l(PQI?*%SG&WSWd-}x{XXn2U2Yjs z_j~1|c?Lg1BfDtl1opb>xARyB+n9Di!_0f$>*Y3n0vdBZyMNWIXRDuCE)?c|>k##| z{OkB#?Yau4uX6U4O^<%~`Esw*ksI1ST(D}Atg0k&&9t1%R6)**u^ZcW+0rdrgv)4a!s2rxV zU=R;!AE^3UHn?Q(qw1Z%*8W&{>RklBuQ%#hhXvH zj)hU3C@sm`n3l~)lCd;dl1hj$28j~nJzUPPGq+2)m%{4L!v-jMI9wop+$k|7!&p#s z3C*1(fn=3Hb(I$g2mUJuxq=5D&+5U(Y`a5}(tnaL+x4jA22Db67#l(=8>NP5)^&*x zZ9gNaCGh%WXh7kCBjpzC=d%)ctr8!xp=?MZW+Enq1j&(U-;mt@Uu^dW$<2^gv4lLX zC&x=r)U=^GBn3qNXt;_mE|G+j3Db*FOf>+*WA93i(1jr-3($%%qB*C6I7Ht2glK)gVS)z`?aK#{cAvr{ERa6Fu(EIn2Pel3pGJ{ktn#iU`LN7gNrTIl- zjC?+FwHjPR3};V&l+aT;A|FqFkvBzjdYa8`VdI+W`Y<1%;97kFW!nI=X2r{Yd6a_8h?NSuIm3_mxL7 zk#WtXkLgf&v0)6Rg}2+Nl1SSHSe%*yo2Ax9%DoUpN?0NQuvRE@8Whc!+eq!nhG;s} zf*saY8c7Afy+IG;Jq4;&XBX)W0%PH=v#w+X*lz8l^hps)ohS`N1KUbvtXVfH_dXt} z1&P>_{!$a<*H$WJYkEk#P|hk!siU1UWHe-skH+21baKOOEh{+`WNbWhPZ37F98F(?-8-8~Qk`cio$|+B~G%Dejp)>{HwhCE5&n zvA_30--kZ}K2^_KC+J-EXqtC&LZ9M*T&GP#f}UJyW!o;dCL}ZM!t1a|t-P45IEQ&V zrk&AB+a4R@ImrI;$m+@o{@pVBmW+97Qgc$;*0t@-UL((?xvg4xYWm&e-AT?7!!}&K znR==J(Il@nmbX{W@%8pe^z3V~yH2!zf9epa|F0d*;_qa2HCThR2K;Orc(HQi{n5d{ zC0{S-up>G@Y}aPft_>XvN3FPCW0GGmVCBdkm*$MoU3w&O{`#n~Q|8vT_iMFLk}%Tw zZq(~(1Ltk&^J0GWma@^i zY`=yVeqL;zq_7CO$oK{IhdKA-5)so^yJ;-O_7wmKRwe_vN%1RHCZq#W8Ud|_k0s#Vn!}{bf)}PnI%s8OS?oo zJ_t7;pXL9au)`O?i{V{;_`0}DN~b1=^#9%SpbfnXNeYTPPg@?0d}DCSe}g3K!@f)&9Q!o zbQ49u3vmO;riJo=hohz3yT0Tq2L>1^zw-p)$OlC( zg4ZWKn`aTS|Qc$l@2CBk_7k$?lp@c(~1{^h@31beSD38)(=H4hRcf3 zZ0RIYSFxbuQF>~}U*uI8FJemyq}(?mAXPWUf_5@C_@b0v1hLyLOKFz|{1TTvI=n<~ z#qKVY?x*u^up%t5oLy5Yb)%C~dIOrZ6cqh-SNfPfeuC>N`{U1~+$+(r*7m4o8Po}} zLWW*MVPml=FQt=du}8FnkCtq&H`1R3MlLZ3D6&SnNEL+2Bjmbl|F6;${|rY1A|ay& z{gQ?e1Pd2UDKy)_UtQV5d|4Y}Yf`3R=4i)qP~287+ea6%K@n|DnKL&T{OBT%FKdp% zR{(K>u`KmpvCmh4*!32&2#SC*)D}f9gL|4jEo8JZub++;Yi1*}q&lTI$hxxOt!2%r zvheX3K@eIHXvHQv%aW;LOFo9!lRMZI3RF61{{ICf1pnUS(gWzTh#wWb5$ zukNU=hwK^NZ{&#%p^WvENzkmdaDh3tv+OI8r$n-Vw5A~ZjwR4rp^x6OGQt5MEapJD z!$UimwWmyo~nRc78JIEA-ZlgB!5XezGal%yh?b2yM3AV3{`6gxG%;jAu6tkYpIEd zp7}Rq+U(2`vQ%=iz{NdisL%RNkh#!%wFcv+Pm|eG4I5x}VX}0(If!$QkAa6q&lbw^ z={ZGp!cSMqSUO>H!wg$g1DR}u^V5wrva3`jTBAqHjFG4Xw?k;9ib2;m%1zjA8)Oz_ za@aZ8$Y|MEI#pFs<{M>M|I6mGcp0K_@|KSC7MVV}tKjTQ>=6e9edXoT; zwo$4qmLlk#GG5J+v*3@T(>e8MZ0Fz%o1HFOMUHd$VW&GgBU|<#@dly{@g6*!)jA=w zBr|4#Y!{t-gCh|V=8P zR4Tn<$I18dBN_JvVx>9Rurk?QN=Ki&V>g>uA+sVomE7oJ$mAq=A?kUBEQ4yTI?J*o zm}T(`*;>kny6b0|A=+5YEr!lCyvM6$nkZ_k+?f4cE#tn6MDNT$%jmbNu~w|r7g-41 zsLDLSX8w?!CfN$UXf}*k1HNPCm^c?2$mDI4OIZPrv7^G_duF5m$G6Ll(B4S{7BKSW z$_6U07Hr&)J3BS~Dm1*I-oC&gINGi;?}614WV4T6W-d=Zp9SSOoCk;=v~Q@;B+17 z&e$X9QYB$myD|^y4kG*8o#Ad)u<2lk0eaJ(X^zLS+8vmW^d?P2Qwq7$-o)9vFnF26S=A5ucB%PG9JWa5QF<5HWZAL7RZ=V3X=89M>0D| z=wyL}E3{xgaayN@f-5MWzu_Rr`cGht>Exi+p=2zO%^)7kf)aVeqwp)%*puvtc>J0* ze1J-6F4LY$ho|7d|D%w0=bG|>OX(Zm7u)Zi3!a3(h_3p`3kq`j%zgAS#u+VJ!ZbrW z_rbdtGZr#4iNx}SOcwd$*is;{+$YyW(fi>Iit0t23%V_4P7?z7(kFRi0bP#(%BKh9 zwuo8A$dSep#*+~EEn)J=AKFWqUG+TE$sZv7U_Gio^2(G;(3lA3KLP@Fln91#8t5N} z*H%CS6I2n&bRblw%bAWE;7Gp8HjpS}In$2d+B(Ti4G%zT$ni6$JNftH@%7Y{Z7@;c za>i8pltUuxRg69v^LPhi$}>aOE0{Lq^MDobc@q9?j@GPTv@tNTx7?JMLtu(`e05Z| zf}v6N9T4*wfufY(S}{d+0%_j$!~~g(Wv`Ki5Tt z=^IK;I#i-5+vHlrjLNohNP9IifryNsEjQ&)G*pZ=GC~Jea}u$g_y;i1%;6u?`eHHl z6R}j0`4tbaMkd?k)L{N=fJ;v!0Cq!`uQ=8LSl&X@#BfFryVeZGMWSqN0Tr zylAKDFfK8GdyjE8O=di%>-Li|Fw-pfX|FQlZdP~z5=AkVWIf_^Tf4Y`*Cl{~JX9!M ztudtnI^HRkT5F=nC~j)8Q6TqPa}H@poQklT8kVc?1#I@iJ$q*rRu@_Tbysv^J;x$s zJ+PS83V`htXIqII5Z)$W>XB^}rEMIL_6CmHasyE3U|#mgAqI*j37<-6a#$ZXv8LN2YqS#)+!AouLa4fE4HaO4C#WF#Bx$M!~&lb zeJvw2uaaBn%w=9|1z&KFZ=Z1|FF~_;yQR3<=+$1OOhKr_=(z>}E^_V#JKa zfUr?yE!IL-3QnMt0uU#bkr8#v^c!ZHgb_Nc0pX+=y;_cH>PP&=i1X{n2w9X~gB2(D zwhzsL2$-h%uN5c9U}|i5OQKE5CSb906JddS`nBB5(DoIbHv=MdGofC-nVVEH2JYRa zq=pWF)C5c-AkL){D@u3i5uYdi8Q0Nf&;jtvV)$vR;-Bs)E|KX6=kcDMQy0w`D$H9*YkX3^Wj=HFTseY{S1< zim)TLC}|5rbH?lJ`H`LACXHvRrq)A6&mkym3)5OrbB{saws3rnlYnnsF0g2gf|HmQ zWU89m_Ab(d-lGHIHDPZR7DrJ^5;KrYz}?7a`#9LI+aZkfR3U16nP>#f2<-_w!|{@$ zU%<{@{og&vN0D2ZzQl4xTS1YD#dxG*Uoku-sgGlwk^#}IlxVmtnXw_lU%jxKI0=LY zHi=;e@1fKrrbEJgapE@49ksRrhwz8QcF@z%p2+KV6@Vun6Ck^dX-0t8F{Q>641}(R zCd3U1;>Kk_*q1fR0VQQZv}`*kA!a+E*QuiAs2tEFjo{XXDDDcR5r+yD7E)+Y#TXC? zBzvh>Dvng@p{N}kUy?%oFwh7U)dNWi-GqT=_WvXs-yKgK`uv47A;IQWS(Gy5y^||S zm+k~@+rL2Fb~5IQSuZq@$4xF~>Hi5_8V|#7gK1l-3@?3y$t`gIL<6z{pBc2}E+@`@^6MoDFg724RJFbHh48@b6_IW# zXCjAGV3hw=iAne-Z<0|X1^Ll^AnHa!ys%YK>)sv>Fdb=1?GOIQ# zq>mnh0S*h))LN?yk5ofp>5Lhgx{EO(q+{CZorwXmkswV~`J;SF3f&!b(0X{+oGfq! z$lz}gQxtT1G$&Ax-2h!Dqfp%I5iQ)!O*bB+)~Hido;oyMEZ@!f#RrVaHl?VyraUbY z?1@^vYvj!~gXUM^M>R}&*&|*K(*ie$)Ifjsb8FE3%lz>+u-uDaEmddi>_GWHO5!@qIJdJp4H>_}<=yDsQs7Gs09?&0jTZx3)?%0cT}BT5l8 zsf1pR8|1Xqty0V~SqQUV3NK)~G%$z1?t4;x(7;j?g=%m`xzQH4D@$!Ip z#v8E9Wai%~>JHDv`wL5ohBgum2Q=8uE;QU+3eRJdrOBbf|+1tcsD zDu8Ee@leO6YkwH{daff#)MW#7NXG!9A3VY(1iy>ei9(0)AJ94*RW%*Z0gH+x{OfP!X@ z*}wU3^kFIq`iW{I=_5|R8qB44EM`In z@jMj*tw(jxn`4}iUl{f7B1O3!=MqW(;{c5<#JtQ=ekybzgGQuM(qP<@qa`&pim5e1{*^>@wnolC!b_su*RR z;B1wLnU8&`LPL*6)+Ygjs+1T@wCE(awL44?_%i`Er9Xr%ca{0?tX9%X(9>$3E_wum zhy%UG{3gCt;-O{2TW}4f>-wJp{LFV0@0<(Q7u!!Uy2RdAfBbGVhh+(ZP~xp3?_rIS zp9H>$#IrTJ`Mc-``id0at^LSp~WhVHybei(AP$I++-C&lT~eG4&6Kai)6GIF`9`V7YF3z7ACsGpSWXrUXcMnnFy zIs-J`A|;Ikg`8omh$az-$IiG4?r8@$=%!+WEJ-8!8VTQuL<%x!+N%Jw?XXbyAgQZ5 zYPF0G0z>N~@3TxZ5(s*B?zpoXEUKG6{HO?lbv?^;CrlLuvG-mB(?Cq9LbTpM$y5VD zhgV2VD>w&bpZSCpf+6)Gj`vU*bjO4af{w232>&6|9WpY2U|y<&B9X2#Rz%jb89>*A5iYim7~C(*a!CIb9W|r7@uL&2oFA>j^yXKHM2Sg_}6V3 z;bA;oj}CoYS0#dWO)Jc2_LQEK?dQ*NY5OT=b;kjtY&8G59CIoFoUJ3qwL>2Ym}bNd zhGVB_U^{$*A8+_+_;cfg!}9z?xy z!QT@FxzxII+t2cW?fX;2$wB3HM@w}0b%9sXtwD;L-#?~iFTKnFe9&S=T- z0Md*WE#D^B=ar!&7q~ksayfXc6VkcLm^sgxHO1-QL)%kk)~QyYdTWrSde(ZHNyYs1$}Dq%_>=1tOA&Y{8POX-iWlXa#q1-dEOKJ zxjAk)D{7KyX6=G9$)yjTfHi4JiymI(6nus60iqhCoC~=-*BgI4hD%|)e}wf{U6&L; z$||mqyY9~|1TM`6H4*?#Ze>XQ8Yji{8sO*rgEz+=cXi1$w@j!L7{WYvLX7LrO_%;U zgpOQe=>Mvsbsd;^2C51%Cmr$7;_IBGC``0Cs1Z@gb;g;jZF{Rb;n>IOnh5C=+=w1D zH%F6;xInSA2>87TQN_1LZ;Lp2zcF5Yf+}7ExA&#ja$z?B?;EO$mmz5NYueyIZ!ppO zsj5Wg2pRxO(64*kIdI*o+#drC)zm7%Kz&tg)E_M`W=0TKe^LybbYuSI)UAZkBR06L z&ENhDY%mtqRMo$4#HkK~E)?;6GqSbF$bf0*#C9-J2vxA^67|@mI?^03n!|{uZ~6ZEpd;!TXhX8QQX+Z-6$`4u$ znHBtg)h*%)bp+Y*m7zXzP}-c%(r#$B5!=dxJavv(lM%#!}g zdGwus2YrS?gsJR4FwJ<-2=9NNJ8*|%)cc2xP-Lc$?sG#wVtoH6s(ARu_k;S%;Q`=Z zRH@>P(CG(UWl)0g&;Ka#t(x4y6}R~^Vk6i|3jXJ%;tGj;#CyWDLit6|2ovJ02$l1p z_2HpH&Q@6ufp@&55fQYJrWa-81~!wyD5oY8AFm91JQ!97KR+6#B2g~KBf21S1A!T- zkjZNPs%qW^my{J?_g*UWmFkop8d1|rhpCT2B`Ylog-+1sBwlNLu)UQ8gI7R#>8-+G zQsNVaHg_Or^Xd9{WGmc>T7tGc;pTbZ2{66VfYL|Mq}sHhf_E$f{5C`7P@2Y5F!a8$ z5~`2dnh50RH~i;CWLBNaEB_Y3tX_d)eN>c!I99KPKKkz|S3X8P1%AF3N)%~mP0L}| z?(rS7fz>@QsOlX`0@5kx?oi0>(;++LQpq?px%In_%r)Nt2fSfaO)NpB<=ks8b`_w( zWJk=QJBq5{Jai|9_H?FD=xI$;q}%@ER_{O^1sq6J89qkNl^nyUN*LWo^&F~L2s zp#gU@+;N*8bRy{@Y@dTTgTi6AezFM8pu8 z2~cYorEpBV-vR!^rbe1TH*P~R{jGD`eP0Yg@Qn#PE>yA6E!2n~F|65pt_WM9v$<*l ztj11Qjh-rl3R4>ifmYsh>)W_Iv+n@rdI&pJ^)4a>oqNw6vzQN{WS0zXJhYxh(AnFP zEM&ljwIO|BjlMQnqkbs<1LH?#@-g~(XI&uhc2ZO8uA&tGn3AazA=Q~3|7`~7UjzE9 zhN9IMl#_NRq}Au1Tb4lx>;exjdZ=&>y{zOaN3baL!RE!`p9h=6REL30hpW)D*ExF5 z6BxAPBNxcee1xfwfD1Uh;~E_Kbcq0ZDtzJ=vCAhQa=N7?(#Ji=X>L7?QG@R^;xGTq zmG6$90k!$AaunGk&Crg|9M?k_zwCh$uY((q)5_oC3!ny-aa{3&O#Z^5qA==Dg>n?p zg+VXBaN+6?#&>?9#9KEU1<2M2`wDpVY9*eGm!YDs+yz4Fpo5GItoRrf-B-nmF*Te% z-xxQtKuarAQYv9Bzrk9n7Iw@RjuflD^Bea{-SuxEdfa!VXb5r8$QMTa}tZOtBH!~+kH=@eh!BquC;NpV};DS*_>x*hg=Qra(JD`I+=y+8B zaQny)V5%*n_+LL5W5Q}f*u5t>S=|RW8m+?0QVWv?peQY9-TGib{hjhp;PSf}MtPyy zpIq7tuQp1>VQMCXDb;-%U`R=!Upn2QOXpd-;|scXQ1huKFcXyei}5G>@NkQ7(ImB|Y?Pr}gHNI=m7-jZn~a-2^?YpVNDc z|Iw=vzH4uxe#4xD{sR8cK#B*O(8n#s7#aol4@!YtFLzq*rM%gfqz0}s1fsZVrhB+K zKu#Yukpg@GTm)bixUqYaDty6JwfzBL&la$qRIe}hHyVb!v(p!grtqLp7(1?kBSwi3 zG<}ygxxp#Lw6T+ww2r6`Jme*=G2YH*pM1Qa=JNa2ox~A|s4Z8wqFk@@-(j?*wYz#0i#50NFhY3o)nd z<@4*u6=PIF14@jvq;i_woTPxtJE9yVM$kx}US)fv85te}b&SAkRo}lXJgQ1h8qd>} zT=4h*wRI)nIUZeGghVpgS0RZYk(I1fL?S^%E3qV>C3cCWw04OIA+&_pV#_oL(pD{> zDqZbswU)N1QjMi(>uXUJ(pp;l_nDbI?>pY#b-kA-bIzPOb7tnucAop$U&fK?5Ptd{ z(9a(mndEFfb$>}7o$Q99_E=V!{k!~$WEJr78|;`IhXW?BT28tw1;4J(5PsH^;n>fnanfhCI$JS6v>Pb$Fl6KtU$>ObX&9hi(7b8>`I)?dj&lD>kY zq+2jyiyx9hKek2wKk?ITIq^90hwy!lOkVq6A}N%3G182!=% ziUxN3ky3HzaS2mqVF%U9CtRR;jv^6mKR4n)!(BnzSY_lA$o;ZxuaEZ3B3(CFRH*!t z_#Q}uT@<|E4Ql-j;c6Y&0R!M~n2b!ReqC$K|muj>#x8BF?2*7$}Y)P-+z zXL7mK^?sLF3Pr4k-J%{wkd51GvNFCN;Kp02Xdkgd_WWH^@Bsj6#A5%DUwu#@T^8yg zL$51V`qTi&rv}WfzgM+zD%u83FXPt^KhwW^x1@Yzk`a&HJkfsja1BlRAXR*cz@9sL zsixmS^jKU&6P-Rw!2j}OfufiDdV#bMH_k-DhY4gi5+RgJt7R%aD1fk{T3IJ4!_Ov? z_&$LnFAZ0N^G4pZz8j(Ias2dNV2^uiDy8=01=7Pj&RFBO6})sUrO#$AH3q*tC41_P zq62Zet)$rV5r5V5;di}NHAgoDJ^F7+$43qv_=gP@Nd%EKdnifxd_jXUA4PHYL8e?2 z#fNW;P^5`OD#e{Rl~?;M1i3@MM5J#X8zk{L1Zg{QWbyn7&9D9hLofZeIb6N&7_$B` zSk*TZ=+;p<3UfSqy|cPK(=$n}#H%1bKO?Pt2$8+M>Aec!7$Tob65-cdOyz5X!{)We zVrwWCTl#P-GRwvfJjd>W_9#Nb+?W`Pu!ohw$$l(RbW(B8Jlcb@2J$vXI*zN0Oh^69dBLP1!MFnq;@flH#~LVJN;R2p^RM<h1*XO@u|DrCPmuE51DFgNjtD?0 z37uss0Qco|wun-H=pj>qwlCMF)s#A~52Z#C%D#+^60zz343@krn=)6?U_GmnyQksA zVj5O!`f$q_B3TJzp96az-@R|9EbacqsFzC7K*yoCe(luYb+b$W=64gBgq;eTK`ZOY zk`8=+>8kEf>X2NS3UK}a)6w|A<{(XZ;w!iunlDX zg7wRUuko}WIBZ#paqR(bA(XS8Ys{^e*8eKL$a zMR~&MqXDviEq^0$WI5OdH++%xC=Y1CItc@Ov+C5myV!vbLF4L!>A+1u=^-| ziLVU?&IYXW^}%EJTdlfw+%Z_OZ#+6KBObM@g0}jx=D2&c?D!-`Fq59TA{b2>_&`wj z)nL|4sOPZ0r^{FfNrezyy_BbtwR4DCs16Nbjjf&Q*zjIKtR??%2vhF^al{<>TG{Z! zF7*-cMqe*@%|pd>?5*7L_dD<;e%KbJXd`g4mZA2u269EbAQT17uWv393@t>I$jy1% zn_PoLFLZrfVlj}(YE=m31H;sWL2qqXS20Bs-!=HKmUbWvv`7bY8oqfT2SrpkXsOO7 zT9BAe#V}e=w7RY)8omfnqi#QFnzr_AXv3o$+63`eYv--32xJ*uoBS*SC|BW)lvmhG zwCMpV3usM6RqvlUR~3h*aY1kItr>53kcros+qJN1Ci<^%df$0nX!9Ak9WZz63;7BB za7(q2r<(!mrJ)Fqr#Ku;i1s|C*xpqXYmQp4Zx?Q98Ss$>`R<;`We!WPPdaBK%!@%T zfp>5AIv-vDjTB}b8p&jMIgexsBISojmLwh;MX}D}VOW%!f8L2w8?;+dtdk%`MYB}# zP!O$h?T=>iMK+rlCIgW(Vw9Zn+J|c~iYGKy37iv2p543XR7Eni|WLXYg1Shjee!yJC&C6%{XYnCgLSGQ*JHC~68*(8xN`(>6V9-h3+ zMu-Pmx$wd^teyC4PaBpm9$sq8-VhJ(wPlmVL!)*o>xy=4ut=$C$EJt}ipSye;#nK< z*Wq}THL5)uB~n(mSH(LguyG=#D1nU@4|ft2X;`8neV53F36hZ7tAmodr~{J+iVr)m zk%CTh9iN`W@E=$>Q}_ z1H_q!cV<~4$$((y%Q~~6VkFRz!SE6PFaw$WJI-BFSaae3D7BUeZrtpjgAqR9X%M^$KYi0sW8(KWh3AO(%+5Pg z>A2H$v9&XWeUzP!3?ByXR}Al`5AHA*$sgru>(29CY#mHt-0f50_-ot^x-(DofPPDA z!@Mhq{+EYA_u&O4vdUuT^qVJ9LM*PF!%M68zk@1W!cSkdB=QMeSvS#c2k3V4Rc|4I z13}uE7lbNvHhl#oCto79BoJwNN=cr{DvVdYqI&#Qa9PczzgzLsen2s%*Sw^AG((Ek zJlv2HsyyiSrEZHJA~^olU){HTV6tk z0(3Xu2~{=yD=_71L}=4;@6`TG==C!2p8$VH6EmL5nj59K^V`kQMD*&Z=!IP#oy?>d zqF{vcbkj1e4Kgx?@bQC`?Y5=CcGY2$bT^H~2q_om?^~M=DSI7WyvY3}!jR(5fnV)m z6165BqP9dEq8by7bt5r=j?F&jP4QO?vBpW&w6f*V-I>JFDCv&;MQzRG1KC<`ef}lV z=ZTU8SZn#>%CH_tebhy!0@K=)CuFISGH~|A_+uz&73|kpEAY*9Ly8mk?y0uK;(~im zcS*Vicx#IdRO_IwnM zOC&$gTjjzozBi63$qMAyK*5^5=fn;9Ve%Zdno?~mWGeI$sIqc*m(>@f1?!0vM3A!0h20KQ z>izd*s&zjlTv&4=PwuB?zZw1DrB2e{5&VmO>bU#Su9)%-So!b5PDwvfyt7lv1Inz2 zsfonquzDY#J%EK;%rBK$(4*^qB8KsG`vLY!{Y@C9b*nE|ML5=}Qxp7Re=9m5gmGZf zhKW{poEmHwh_>>D4PVtv-Go>6XIY}kPy3^&z2C^9IuBqn1}krk!=|gql7;NLAN-aV z4PY4}E3FBv{CM>MHS32D#NWB}cQ`+F20-qH2de<3Zqx;7hgytUI}Y$#dtOkZ++x+Z z-H{#8D?;HGb#+@@)N{(^*UUi>dgZD?lCUg)Q0m%Wj8r>9x0NrT+ATT$obDy>;-`1L zx9=MK0`Jzx1QU|~S*#tMCog~{lJLLj-#bp{S|fLn6iSuZJps zb%|07MQSX+{19Hj=sKfqR;E`#|HJs{jk^00Nr~kRAKSW__(QkNy5WmzyIb_1uO7=% z0m0kI)XMSMD0;v@Qq;n1)Un*6Ve6^l%nWsJ-%z{NailIhmNfQs9R5IrRXK8qWHQQWg(K zDJ6E4HG~t0FRys2I^W>Nk@=)$J!q+W=u6K0`bZYaqg(*XI1?|5iCP7vxvq48Y8T~dY!tF?X+#3saR3C{Sj>wFPad71>x=sqdfaU)^>{K$>EZesgaroi ziDOxugH+86?oAVvr*N zll}^wx@&E_FUk<$!OsA3Sopn8Za>aA1inr%MTW-zODyrH@vp}y)*(CRr`uo^j~ zA!6W5#M+VIh2{D!HFK}kP-xN3LW^}tQVYNldlH_pt?8Y8!&)d7ek(aHJ{rJF)L(YHaZv585r%5=z2cLUnmn4i zMmz_0GQKN5UNv!t@gQ9oBYVxe<5jQOL#dm`$_zkKb~7R3G>fV4G7-iKgoTqdwSOHa z`4>)54y|$mq)avDX z@aGey|KgPw13yo52_{(`rzT~>q^mU_Sp;hNCF zizNT{MS!r4$W?V0+vB38Jh=d>{h6mypXEW}^CC5#15MY7znZUl#OA1t`!LE6aD2`wuw9;qB#B!MlP`nbDC<_mDFRr1~c6>*^ zvenMCO#Uu1zmAoezUx27uji}dsFu@^^(umn3s?u9HiI?bPYaYE>hXTxlXSXU0?qZo z9k;{KTA=kd7QOmj{Fq&7SQfkBQnZ_9#y@ycftv0r3Q*ohdyM=tXsW@9wv~P;>I~G? zXSz{G4A%M_z`izo!gOWuS<@k^`U|N#PYdo1rdw`VEpWCcBS zyR^junp)$;40SGgbp`}PpQSMv%tLOfXv6CEw`z{Sp_O@IVW?HK;sUYy5-_lAnI(9r z``HbV3W&_cJaV&&->D{w?M=xqV1nDvR2Azs6Qmq-(!rZ>U*q0XJtO+&ijw1S(1W0Z z-ZO6BGUUlOO#5VvicJISEF6F9h7G)Du)1-84Z92Zq9MbQ4i&CLrv4V0uJE0+m<*r& z#y-QlFPO@}q$}~;V?(k#?_Q*i`JYd6sffj>J%Jw5Q8U^O+**>+gb@zZ@{ey&_24mh z$6(Cp{F!xm^@3V{fE#<6Ec099xoC=|@jyS)Pp9_Tn`Hw0v231wc&Ph{qZtVR$llx^ z@A8VNsO22c1AWZsMLu@2B?P^k=-&Qj^k3$vZJYg@ApOzIj099<04JgmMb<*19}G03 zHx9CsjUec^h~6Z`j1J6XPgDM^w?LmAVMf0aq3EKQ$M3hQrgf?xY^b+<+bFY4MRS3G z8!%V7jKn@$IzK?;G(^TOS|fS#UUSt|=a#uBc}AQR0t{wXQ&=f^Myqr_ao+!PS{IQX zxA`b!4uDtIhuu4rL7tB9p9k6BwB={ZFj*zV+u8H-c?@BC?d+oCZ;6Xun+;ncW$(Eur0GTMSZhz9a##+FzJr>am7#W#~bF zp%8uHQ_MFkRtul&l(k@<%nImgaY!S!)zbl_LPZ@~g1)(Xo$8h@?;Y4iTeThdC26KO zYvPX)<^ONR?n$0t`wc&RaN?!wrOLy0Z@J?;a%R@(=DL!{7Rz1*;A>mHW2v&j!KKi5 zXQ?FFl&TIV?y=umAr%f-Y1e=cDpeWYC`E<~CX&0US*uoVblQbFG=hTqk=hSsLR8r@ zCfDWR%OK8Fkp}h(azlaU3FsD zV>u`R>&)a&UI!OUp;e-8k%p{P=X0}(VLNcN-WNM5ePR370V9)_Df%F1 z)b=9_P{5BC-F)RK*-e1(Ezc_Y^{tmv0y9#~y}n*A;7!X^@Z*p&RBG^dRFp3Pe)IKZ z%J&Gz|0zFH#^h@-+Xv;RWutoeD7}+57VN(#j~dK@0Pb%J-t6~x_9fEvs)eRk&Pz=X za;0fu=ko`%!8!*1F-#MElvi`sR(OP#Z$s0vI0HUsA`R011ZBF$yC6Q%gvuKk$4#}y$-*=3B}s4RWR)lf?Zp|wzFmD^My>#nr6~{MJAOeY2x55Oe4ZkTngRZfW<~ObV zmJz3X7w&ll)VORz@;VYMxmobij%K{qdQ6^%-|e?*=6AGF8U%rQ9Hnh@$?CM8NsO(| zmoF55idA#C#VO2M-gCWL-v~ExESg(w!ZG}<^~ys$UJs2L#>mvd-gx(<#|Cw_Q~mLo z)8xsM&>wVfo)TxqzYqK-%dc)NNuHIea$%Jy;DJg7jJZ5IE~nK0B}rZaHz(+G8H75) zzS+CwC~`Jd$ohh+Pl_4;fOk{@bTnSKZ#O|AKy-^EIc}SM>jTtdAN+t`j}MJpzB~!D zuIPq$*3UlPhpG&N>g*@dYYTDaz*BcL;gxK77dkA;G?Ax-s@-OGlG`JSl%mKdJ(?_{ zx2z3}-bj=#j3{p2&i9q1SsM|Zw(mX01nDGhaO+0(Gf19<$Zn=Y!bZ?H_BZJA<=8Eh zI((Rs>RI-_nu5euZ8u&w-Nag1813%oxm&-6!Ome3l%#cMPrPZmiM8ja-&d>a3XQ}F{(iAS zSs^TUUbLBc)qN{Zk}l@~_usgFzK&ehgQw!f`swxo1lTTPvwCbn^53<|wLtGd;+{LH z`+-x`!KixOG`)}JD>*LH>?16;-$`2`GjE!yD1i;%T*$n+xj{V|tYn7_O{Q0Y%5ZNh z>mYbPc3V1!$}Pe#S>yd)@^<9Awy0rR`CpW|WV#XolzHiQJrk=2rXlM>{Pg3|Xdb;y zU0yES1_M8!znk%-LLg_}*`}oa1B&(Ag-R-{@14-qpfQf69=B~g3NZ-Tfpe-*nO}&FWrXa TXx}1p(*pE9$mU;Ki`V%-jFG$N From db1dc0917c6fa4dadffcfa477401df6a5e9f51f3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 17 Feb 2015 20:08:45 +0100 Subject: [PATCH 147/264] fixed tractrac pom --- java/com.tractrac.clientmodule/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.tractrac.clientmodule/pom.xml b/java/com.tractrac.clientmodule/pom.xml index a9ac043c7fe..16743da1b7e 100755 --- a/java/com.tractrac.clientmodule/pom.xml +++ b/java/com.tractrac.clientmodule/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.tractrac.clientmodule - 3.0.9 + 3.0.10 eclipse-plugin From ff36a8c758c8b9814233591725a3cc3f2d9e1efa Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 18 Feb 2015 10:52:01 +0100 Subject: [PATCH 148/264] Added commons math to launch configs --- .../com/sap/sailing/polars/mining/CubicSpeedRegressions.java | 2 +- java/com.sap.sailing.server/SailingServer (No Proxy).launch | 2 +- .../SailingServer (No Proxy, Cached MTB).launch | 2 +- .../SailingServer (No Proxy, Jetty on 8889).launch | 2 +- ...SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch | 4 ++-- .../SailingServer (No Proxy, Jetty on 8890).launch | 2 +- .../SailingServer (No Proxy, Remote Debug SAP VM).launch | 4 ++-- .../SailingServer (No Proxy, winddbTest).launch | 2 +- java/com.sap.sailing.server/SailingServer (Proxy).launch | 2 +- .../SailingServer (Proxy, Jetty on 8889).launch | 2 +- .../SailingServer (Proxy, SwissTiming Live Simulation).launch | 2 +- .../SailingServer (Proxy, winddbTest).launch | 2 +- ...s-p2-remote.target => race-analysis-p2-remote.mist.target} | 0 13 files changed, 14 insertions(+), 14 deletions(-) rename java/com.sap.sailing.targetplatform/definitions/{race-analysis-p2-remote.target => race-analysis-p2-remote.mist.target} (100%) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java index 2e15631fc45..eb4615b9729 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java @@ -19,7 +19,7 @@ public class CubicSpeedRegressions { for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { regressions.put(new Pair(legType, tack), - new IncrementalAnyOrderLeastSquaresImpl(3)); + new IncrementalAnyOrderLeastSquaresImpl(3, false)); } } } diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 21627fd6acd..dbda9a5f9de 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -30,7 +30,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch index 68dd7191370..dc792e3506f 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch @@ -29,7 +29,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 3ec0f77dfc2..196a814b823 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -23,7 +23,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index 7dc838a1181..80b79092727 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -16,14 +16,14 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index 4e7742996a4..60c3aa87341 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -22,7 +22,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index a0b813374ec..8e5ab2a15cd 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -15,14 +15,14 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch index f14b6637666..b22ed041821 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -29,7 +29,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 6d252d818a5..928d278fb4c 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -30,7 +30,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 6d2ff20591f..20e3711e212 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -23,7 +23,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index cfa760728db..deb9cd876a9 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -22,7 +22,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 0f9e209026a..3992002f089 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -24,7 +24,7 @@ - + diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.mist.target similarity index 100% rename from java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target rename to java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.mist.target From 66688f063bb3a2ba3bb3d81ce287091cee2bcf5a Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 18 Feb 2015 10:59:45 +0100 Subject: [PATCH 149/264] Fixed target platform name after renaming it for eclipse workaround --- ...lysis-p2-remote.mist.target => race-analysis-p2-remote.target} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename java/com.sap.sailing.targetplatform/definitions/{race-analysis-p2-remote.mist.target => race-analysis-p2-remote.target} (100%) diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.mist.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target similarity index 100% rename from java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.mist.target rename to java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target From 3cf76f3a88664f2f08bba5dfbcfa7754055f89a9 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 18 Feb 2015 12:57:24 +0100 Subject: [PATCH 150/264] Changed order of speed regression to 4 --- .../com/sap/sailing/polars/mining/CubicSpeedRegressions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java index eb4615b9729..86f865723db 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java @@ -19,7 +19,7 @@ public class CubicSpeedRegressions { for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { regressions.put(new Pair(legType, tack), - new IncrementalAnyOrderLeastSquaresImpl(3, false)); + new IncrementalAnyOrderLeastSquaresImpl(4, false)); } } } From 87d11f969ccef871057eda08316247b7c0a20bfd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 18 Feb 2015 22:22:22 +0100 Subject: [PATCH 151/264] added UserDTO.getAllPermissions(PermissionsForRoleProvider) which accepts the same provider as Shiro in the back-end --- .../adminconsole/AdminConsoleEntryPoint.java | 8 ++-- .../SailingPermissionsForRoleProvider.java | 2 + .../gwt/adminconsole/AdminConsolePanel.java | 12 ++++-- .../security/shared/WildcardPermission.java | 16 ++++++- .../security/test/WildcardPermissionTest.java | 15 +++++++ .../ui/client/component/UserDetailsView.java | 17 +++++++- .../client/component/UserManagementPanel.java | 5 ++- .../ui/client/i18n/StringMessages.java | 1 + .../ui/client/i18n/StringMessages.properties | 3 +- .../client/i18n/StringMessages_de.properties | 3 +- .../sap/sse/security/ui/shared/UserDTO.java | 43 ++++++++++++++++--- .../UserManagementEntryPoint.java | 3 +- 12 files changed, 108 insertions(+), 20 deletions(-) create mode 100755 java/com.sap.sse.security.test/src/com/sap/sse/security/test/WildcardPermissionTest.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index 6c6261fa5e5..5ac0e2266e1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -19,6 +19,7 @@ import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; +import com.sap.sailing.gwt.ui.client.shared.security.SailingPermissionsForRoleProvider; import com.sap.sailing.gwt.ui.masterdataimport.MasterDataImportPanel; import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; @@ -49,8 +50,8 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements } private void createUI() { - AdminConsolePanel panel = new AdminConsolePanel(getUserService(), sailingService, - getStringMessages().releaseNotes(), "/release_notes_admin.html", /* error reporter */ this, SecurityStylesheetResources.INSTANCE.css()); + AdminConsolePanel panel = new AdminConsolePanel(getUserService(), SailingPermissionsForRoleProvider.INSTANCE, + sailingService, getStringMessages().releaseNotes(), "/release_notes_admin.html", /* error reporter */ this, SecurityStylesheetResources.INSTANCE.css()); BetterDateTimeBox.initialize(); regattasDisplayers = new HashSet<>(); leaderboardsDisplayers = new HashSet<>(); @@ -218,7 +219,8 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(deviceConfigurationUserPanel), getStringMessages().deviceConfiguration(), SailingAdminConsoleFeatures.MANAGE_DEVICE_CONFIGURATION); - final UserManagementPanel userManagementPanel = new UserManagementPanel(getUserService(), StringMessages.INSTANCE); + final UserManagementPanel userManagementPanel = new UserManagementPanel(getUserService(), StringMessages.INSTANCE, + SailingPermissionsForRoleProvider.INSTANCE); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(userManagementPanel), getStringMessages().userManagement(), SailingAdminConsoleFeatures.MANAGE_USERS); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java index a2978c0a121..6b26b66c14d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java @@ -6,6 +6,8 @@ import com.sap.sse.security.shared.DefaultRoles; import com.sap.sse.security.shared.PermissionsForRoleProvider; public class SailingPermissionsForRoleProvider implements PermissionsForRoleProvider { + public static final SailingPermissionsForRoleProvider INSTANCE = new SailingPermissionsForRoleProvider(); + @Override public Iterable getPermissions(String role) { final Iterable result; diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java index b9abe0018cd..0bc85c68f58 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java @@ -20,6 +20,7 @@ import com.sap.sse.gwt.client.AbstractEntryPoint; import com.sap.sse.gwt.client.BuildVersionRetriever; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.panels.VerticalTabLayoutPanel; +import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.ui.client.UserService; import com.sap.sse.security.ui.client.UserStatusEventHandler; import com.sap.sse.security.ui.loginpanel.LoginPanel; @@ -79,6 +80,8 @@ public class AdminConsolePanel extends DockLayoutPanel { */ private final Map panelsByWidget; + private final PermissionsForRoleProvider permissionsForRoleProvider; + /** * Generic selection handler that forwards selected tabs to a refresher that ensures that data gets reloaded. If * you add a new tab then make sure to have a look at #refreshDataFor(Widget widget) to ensure that upon @@ -130,10 +133,11 @@ public class AdminConsolePanel extends DockLayoutPanel { } } - public AdminConsolePanel(UserService userService, BuildVersionRetriever buildVersionRetriever, - String releaseNotesAnchorLabel, String releaseNotesURL, - ErrorReporter errorReporter, LoginPanelCss loginPanelCss) { + public AdminConsolePanel(UserService userService, PermissionsForRoleProvider permissionsForRoleProvider, + BuildVersionRetriever buildVersionRetriever, String releaseNotesAnchorLabel, + String releaseNotesURL, ErrorReporter errorReporter, LoginPanelCss loginPanelCss) { super(Unit.EM); + this.permissionsForRoleProvider = permissionsForRoleProvider; this.userService = userService; roleSpecificTabs = new LinkedHashMap<>(); this.panelsByWidget = new HashMap<>(); @@ -273,7 +277,7 @@ public class AdminConsolePanel extends DockLayoutPanel { for (Map.Entry, AdminConsoleFeatures> e : roleSpecificTabs .entrySet()) { final Widget panelToAdd = e.getKey().getB(); - if (user != null && user.hasPermission(e.getValue().getRequiredPermission().getStringPermission())) { + if (user != null && user.hasPermission(e.getValue().getRequiredPermission().getStringPermission(), permissionsForRoleProvider)) { e.getKey().getA().add(panelToAdd, e.getKey().getC(), /* asHtml */false); } else { e.getKey().getA().remove(panelToAdd); diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java index ee88454a0e9..046072de484 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java @@ -226,13 +226,25 @@ public class WildcardPermission implements Serializable { return true; } + /** + * Returns a string representation that can again equivalently be parsed by + * {@link WildcardPermission#WildcardPermission(String)}. + */ public String toString() { StringBuilder buffer = new StringBuilder(); for (Set part : parts) { if (buffer.length() > 0) { - buffer.append(":"); + buffer.append(PART_DIVIDER_TOKEN); + } + boolean first = true; + for (String subpart : part) { + if (first) { + first = false; + } else { + buffer.append(SUBPART_DIVIDER_TOKEN); + } + buffer.append(subpart); } - buffer.append(part); } return buffer.toString(); } diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/WildcardPermissionTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/WildcardPermissionTest.java new file mode 100755 index 00000000000..a53bed4eef6 --- /dev/null +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/test/WildcardPermissionTest.java @@ -0,0 +1,15 @@ +package com.sap.sse.security.test; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.sap.sse.security.shared.WildcardPermission; + +public class WildcardPermissionTest { + @Test + public void simpleToStringParseTest() { + WildcardPermission p = new WildcardPermission("a:b,c:*"); + assertEquals(p, new WildcardPermission(p.toString())); + } +} diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java index b7144c87f7a..76ad400b9f8 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserDetailsView.java @@ -22,12 +22,14 @@ import com.google.gwt.user.client.ui.HTML; 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.ListBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.security.shared.DefaultPermissions; import com.sap.sse.security.shared.DefaultRoles; +import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.ui.client.IconResources; import com.sap.sse.security.ui.client.UserChangeEventHandler; @@ -51,11 +53,15 @@ public class UserDetailsView extends FlowPanel { private final StringListEditorComposite rolesEditor; private final StringListEditorComposite permissionsEditor; private final VerticalPanel accountPanels; + private final ListBox allPermissionsList; private UserDTO user; - public UserDetailsView(final UserService userService, UserDTO user, final StringMessages stringMessages, final UserListDataProvider userListDataProvider) { + private final PermissionsForRoleProvider permissionForRoleProvider; + + public UserDetailsView(final UserService userService, UserDTO user, final StringMessages stringMessages, final UserListDataProvider userListDataProvider, PermissionsForRoleProvider permissionsForRoleProvider) { final UserManagementServiceAsync userManagementService = userService.getUserManagementService(); this.stringMessages = stringMessages; + this.permissionForRoleProvider = permissionsForRoleProvider; this.user = user; addStyleName("userDetailsView"); List defaultRoleNames = new ArrayList<>(); @@ -169,6 +175,11 @@ public class UserDetailsView extends FlowPanel { this.add(decoratorPanel); this.add(rolesEditor); this.add(permissionsEditor); + this.add(new Label(stringMessages.allPermissions())); + allPermissionsList = new ListBox(); + allPermissionsList.setVisibleItemCount(10); + allPermissionsList.setEnabled(false); + this.add(allPermissionsList); updateUser(user, userManagementService); } @@ -242,6 +253,10 @@ public class UserDetailsView extends FlowPanel { } rolesEditor.setValue(user.getRoles(), /* fireEvents */ false); permissionsEditor.setValue(user.getStringPermissions(), /* fireEvents */ false); + allPermissionsList.clear(); + for (String permission : user.getAllPermissions(permissionForRoleProvider)) { + allPermissionsList.addItem(permission); + } } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java index ae04b102da6..3e44dd457ae 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/component/UserManagementPanel.java @@ -23,6 +23,7 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.ui.client.UserChangeEventHandler; import com.sap.sse.security.ui.client.UserManagementServiceAsync; import com.sap.sse.security.ui.client.UserService; @@ -40,7 +41,7 @@ public class UserManagementPanel extends DockPanel { private UserListDataProvider userListDataProvider; - public UserManagementPanel(final UserService userService, final StringMessages stringMessages) { + public UserManagementPanel(final UserService userService, final StringMessages stringMessages, PermissionsForRoleProvider permissionsForRoleProvider) { final UserManagementServiceAsync userManagementService = userService.getUserManagementService(); VerticalPanel west = new VerticalPanel(); HorizontalPanel buttonPanel = new HorizontalPanel(); @@ -82,7 +83,7 @@ public class UserManagementPanel extends DockPanel { userList.setSelectionModel(singleSelectionModel); TextBox filterBox = new TextBox(); userListDataProvider = new UserListDataProvider(userManagementService, filterBox); - final UserDetailsView userDetailsView = new UserDetailsView(userService, singleSelectionModel.getSelectedObject(), stringMessages, userListDataProvider); + final UserDetailsView userDetailsView = new UserDetailsView(userService, singleSelectionModel.getSelectedObject(), stringMessages, userListDataProvider, permissionsForRoleProvider); add(userDetailsView, DockPanel.CENTER); userDetailsView.addUserChangeEventHandler(new UserChangeEventHandler() { @Override diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java index 8511ea6a4ef..44e0ed0b1c7 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.java @@ -82,4 +82,5 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String serverReplies(); String errorCommunicatingWithServer(); String editProfile(); + String allPermissions(); } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties index 0ff97b2bf48..d94b589bbd7 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages.properties @@ -74,4 +74,5 @@ serverError=An error occurred while attempting to contact the server. Please che remoteProcedureCall=Remote Procedure Call serverReplies=Server replies errorCommunicatingWithServer=Error communicating with server -editProfile=Edit user profile \ No newline at end of file +editProfile=Edit user profile +allPermissions=All permissions (including inferred from roles) \ No newline at end of file diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties index c7140a49800..dbfce0de736 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties @@ -74,4 +74,5 @@ serverError=Es ist ein Fehler beim Versuch aufgetreten, den Server zu kontaktier remoteProcedureCall=Entfernter Prozeduraufruf serverReplies=Server antwortet errorCommunicatingWithServer=Fehler bei der Kommunikation mit dem Server -editProfile=Benutzerprofil bearbeiten \ No newline at end of file +editProfile=Benutzerprofil bearbeiten +allPermissions=Alle Berechtigungen (einschließlich der aus den Rollen abgeleiteten) \ No newline at end of file diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java index 07ed428082f..4dc41bd9f6f 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java @@ -2,11 +2,13 @@ package com.sap.sse.security.ui.shared; import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import com.google.gwt.user.client.rpc.IsSerializable; import com.sap.sse.common.Util; +import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.shared.WildcardPermission; public class UserDTO implements IsSerializable { @@ -44,10 +46,19 @@ public class UserDTO implements IsSerializable { return roles.contains(role); } + /** + * Returns the "raw" permissions explicitly set for this user. This does not include permissions + * inferred by any {@link PermissionsForRoleProvider} for the {@link #getRoles() roles} that this + * user has. Use {@link #getAllPermissions(PermissionsForRoleProvider)} for that. + */ public Iterable getPermissions() { return permissions; } + /** + * Same as {@link #getPermissions()}, but returning the permissions in their string representation, + * as specified by {@link WildcardPermission#toString()}. + */ public Iterable getStringPermissions() { List result = new ArrayList<>(); for (WildcardPermission wp : getPermissions()) { @@ -56,13 +67,35 @@ public class UserDTO implements IsSerializable { return result; } - public boolean hasPermission(String permission) { - return hasPermission(new WildcardPermission(permission)); + /** + * Returns all permissions this user has, including those inferred from the user's {@link #getRoles() roles} by the + * permissionForRoleProvider and including the {@link #getStringPermissions()} that are set explicitly + * for this user. + * + * @param permissionForRoleProvider + * may be null in which case only the {@link #getStringPermissions() explicit permissions} + * set for this user will be returned. + * @return a set of permissions with no duplicates, all in the format parsable by + * {@link WildcardPermission#WildcardPermission(String)} + */ + public Iterable getAllPermissions(PermissionsForRoleProvider permissionForRoleProvider) { + Set result = new LinkedHashSet<>(); + Util.addAll(getStringPermissions(), result); + if (permissionForRoleProvider != null) { + for (String role : getRoles()) { + Util.addAll(permissionForRoleProvider.getPermissions(role), result); + } + } + return result; } - public boolean hasPermission(WildcardPermission permission) { - for (WildcardPermission p : permissions) { - if (p.implies(permission)) { + public boolean hasPermission(String permission, PermissionsForRoleProvider permissionsForRoleProvider) { + return hasPermission(new WildcardPermission(permission), permissionsForRoleProvider); + } + + public boolean hasPermission(WildcardPermission permission, PermissionsForRoleProvider permissionsForRoleProvider) { + for (String stringPermission : getAllPermissions(permissionsForRoleProvider)) { + if (new WildcardPermission(stringPermission).implies(permission)) { return true; } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/usermanagement/UserManagementEntryPoint.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/usermanagement/UserManagementEntryPoint.java index fbbf5853e6b..dab822ddd6d 100755 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/usermanagement/UserManagementEntryPoint.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/usermanagement/UserManagementEntryPoint.java @@ -33,7 +33,8 @@ public class UserManagementEntryPoint extends AbstractSecurityEntryPoint { UserManagementEntryPoint.this.user = user; } }); - UserManagementPanel userManagementPanel = new UserManagementPanel(getUserService(), getStringMessages()); + UserManagementPanel userManagementPanel = new UserManagementPanel(getUserService(), getStringMessages(), + /* permissionsForRoleProvider is null in this generic entry point */ null); center.add(new ScrollPanel(userManagementPanel), getStringMessages().users()); final SettingsPanel settingsPanel = new SettingsPanel(getUserManagementService(), getStringMessages()); center.add(new ScrollPanel(settingsPanel), getStringMessages().settings()); From d142fec9248eba76a4b06c9fd97d1483ec4d273c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 18 Feb 2015 22:45:25 +0100 Subject: [PATCH 152/264] removed unused name property and NamedImpl inheritance from AdminConsoleFeature --- .../SailingAdminConsoleFeatures.java | 40 +++++++++---------- .../adminconsole/AdminConsoleFeatures.java | 8 ++-- .../security/shared/WildcardPermission.java | 2 +- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java index 9759381bd21..48183385ea4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java @@ -4,24 +4,24 @@ import com.sap.sailing.gwt.ui.client.shared.security.Permission; import com.sap.sse.gwt.adminconsole.AdminConsoleFeatures; public interface SailingAdminConsoleFeatures { - final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures("MANAGE_EVENTS", Permission.MANAGE_EVENTS); - final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures("MANAGE_REGATTAS", Permission.MANAGE_REGATTAS); - final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures("MANAGE_TRACKED_RACES", Permission.MANAGE_TRACKED_RACES); - final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures("SHOW_TRACKED_RACES", Permission.SHOW_TRACKED_RACES); - final AdminConsoleFeatures MANAGE_RACELOG_TRACKING = new AdminConsoleFeatures("MANAGE_RACELOG_TRACKING", Permission.MANAGE_RACELOG_TRACKING); - final AdminConsoleFeatures MANAGE_ALL_COMPETITORS = new AdminConsoleFeatures("MANAGE_ALL_COMPETITORS", Permission.MANAGE_ALL_COMPETITORS); - final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures("MANAGE_COURSE_LAYOUT", Permission.MANAGE_COURSE_LAYOUT); - final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures("MANAGE_WIND", Permission.MANAGE_WIND); - final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures("MANAGE_IGTIMI_ACCOUNTS", Permission.MANAGE_IGTIMI_ACCOUNTS); - final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures("MANAGE_LEADERBOARDS", Permission.MANAGE_LEADERBOARDS); - final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures("MANAGE_LEADERBOARD_GROUPS", Permission.MANAGE_LEADERBOARD_GROUPS); - final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_RESULT_IMPORT_URLS", Permission.MANAGE_RESULT_IMPORT_URLS); - final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures("MANAGE_STRUCTURE_IMPORT_URLS", Permission.MANAGE_STRUCTURE_IMPORT_URLS); - final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures("MANAGE_MEDIA", Permission.MANAGE_MEDIA); - final AdminConsoleFeatures MANAGE_SAILING_SERVER_INSTANCES = new AdminConsoleFeatures("MANAGE_SAILING_SERVER_INSTANCES", Permission.MANAGE_SAILING_SERVER_INSTANCES); - final AdminConsoleFeatures MANAGE_REPLICATION = new AdminConsoleFeatures("MANAGE_REPLICATION", Permission.MANAGE_REPLICATION); - final AdminConsoleFeatures MANAGE_MASTERDATA_IMPORT = new AdminConsoleFeatures("MANAGE_MASTERDATA_IMPORT", Permission.MANAGE_MASTERDATA_IMPORT); - final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures("MANAGE_DEVICE_CONFIGURATION", Permission.MANAGE_DEVICE_CONFIGURATION); - final AdminConsoleFeatures MANAGE_USERS = new AdminConsoleFeatures("MANAGE_USERS", Permission.MANAGE_USERS); - final AdminConsoleFeatures MANAGE_FILE_STORAGE = new AdminConsoleFeatures("MANAGE_FILE_STORAGE", Permission.MANAGE_FILE_STORAGE); + final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures(Permission.MANAGE_EVENTS); + final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures(Permission.MANAGE_REGATTAS); + final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures(Permission.MANAGE_TRACKED_RACES); + final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures(Permission.SHOW_TRACKED_RACES); + final AdminConsoleFeatures MANAGE_RACELOG_TRACKING = new AdminConsoleFeatures(Permission.MANAGE_RACELOG_TRACKING); + final AdminConsoleFeatures MANAGE_ALL_COMPETITORS = new AdminConsoleFeatures(Permission.MANAGE_ALL_COMPETITORS); + final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures(Permission.MANAGE_COURSE_LAYOUT); + final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures(Permission.MANAGE_WIND); + final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures(Permission.MANAGE_IGTIMI_ACCOUNTS); + final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures(Permission.MANAGE_LEADERBOARDS); + final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures(Permission.MANAGE_LEADERBOARD_GROUPS); + final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures(Permission.MANAGE_RESULT_IMPORT_URLS); + final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures(Permission.MANAGE_STRUCTURE_IMPORT_URLS); + final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures(Permission.MANAGE_MEDIA); + final AdminConsoleFeatures MANAGE_SAILING_SERVER_INSTANCES = new AdminConsoleFeatures(Permission.MANAGE_SAILING_SERVER_INSTANCES); + final AdminConsoleFeatures MANAGE_REPLICATION = new AdminConsoleFeatures(Permission.MANAGE_REPLICATION); + final AdminConsoleFeatures MANAGE_MASTERDATA_IMPORT = new AdminConsoleFeatures(Permission.MANAGE_MASTERDATA_IMPORT); + final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures(Permission.MANAGE_DEVICE_CONFIGURATION); + final AdminConsoleFeatures MANAGE_USERS = new AdminConsoleFeatures(Permission.MANAGE_USERS); + final AdminConsoleFeatures MANAGE_FILE_STORAGE = new AdminConsoleFeatures(Permission.MANAGE_FILE_STORAGE); } diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java index 18800735b6a..05081d3890a 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java @@ -1,8 +1,8 @@ package com.sap.sse.gwt.adminconsole; -import com.sap.sse.common.impl.NamedImpl; import com.sap.sse.security.shared.Permission; + /** * Specifies a feature with a name and the role names, one of which is required in order to be allowed * to see and use the feature. @@ -10,12 +10,10 @@ import com.sap.sse.security.shared.Permission; * @author Axel Uhl (D043530) * */ -public class AdminConsoleFeatures extends NamedImpl { - private static final long serialVersionUID = 7441608351911944793L; +public class AdminConsoleFeatures { private Permission requiredPermission; - public AdminConsoleFeatures(String name, Permission requiredPermission) { - super(name); + public AdminConsoleFeatures(Permission requiredPermission) { this.requiredPermission = requiredPermission; } diff --git a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java index 046072de484..aacd4f30936 100755 --- a/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java +++ b/java/com.sap.sse.security.common/src/com/sap/sse/security/shared/WildcardPermission.java @@ -202,7 +202,7 @@ public class WildcardPermission implements Serializable { int i = 0; for (Set otherPart : otherParts) { - // If this permission has less parts than the other permission, everything after the number of parts contained + // If this permission has fewer parts than the other permission, everything after the number of parts contained // in this permission is automatically implied, so return true if (getParts().size() - 1 < i) { return true; From 11960f6411221407eda1d20c140ee9093a31250b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 18 Feb 2015 22:50:53 +0100 Subject: [PATCH 153/264] removed the no longer required SailingAdminConsoleFeatures which has been completely replaced by the Permission enum --- .../adminconsole/AdminConsoleEntryPoint.java | 53 ++++++++++--------- .../SailingAdminConsoleFeatures.java | 27 ---------- .../gwt/adminconsole/AdminConsolePanel.java | 17 +++--- 3 files changed, 36 insertions(+), 61 deletions(-) delete mode 100644 java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index 5ac0e2266e1..e7f30ff1ccb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -19,6 +19,7 @@ import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; +import com.sap.sailing.gwt.ui.client.shared.security.Permission; import com.sap.sailing.gwt.ui.client.shared.security.SailingPermissionsForRoleProvider; import com.sap.sailing.gwt.ui.masterdataimport.MasterDataImportPanel; import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; @@ -64,7 +65,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements getWidget().fillEvents(); fillLeaderboardGroups(); } - }, getStringMessages().events(), SailingAdminConsoleFeatures.MANAGE_EVENTS); + }, getStringMessages().events(), Permission.MANAGE_EVENTS); leaderboardGroupsDisplayers.add(eventManagementPanel); RegattaManagementPanel regattaManagementPanel = new RegattaManagementPanel( @@ -75,13 +76,13 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { fillRegattas(); } - }, getStringMessages().regattas(), SailingAdminConsoleFeatures.MANAGE_REGATTAS); + }, getStringMessages().regattas(), Permission.MANAGE_REGATTAS); regattasDisplayers.add(regattaManagementPanel); /* LEADERBOARDS */ final TabLayoutPanel leaderboardTabPanel = panel.addVerticalTab(getStringMessages().leaderboards(), - "LeaderboardPanel", SailingAdminConsoleFeatures.MANAGE_LEADERBOARDS); + "LeaderboardPanel", Permission.MANAGE_LEADERBOARDS); final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, getStringMessages(), /* showRaceDetails */true, this); leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration"); @@ -90,7 +91,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { fillLeaderboards(); } - }, getStringMessages().leaderboardConfiguration(), SailingAdminConsoleFeatures.MANAGE_LEADERBOARDS); + }, getStringMessages().leaderboardConfiguration(), Permission.MANAGE_LEADERBOARDS); regattasDisplayers.add(leaderboardConfigPanel); leaderboardsDisplayers.add(leaderboardConfigPanel); @@ -103,7 +104,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements fillLeaderboards(); fillLeaderboardGroups(); } - }, getStringMessages().leaderboardGroups(), SailingAdminConsoleFeatures.MANAGE_LEADERBOARD_GROUPS); + }, getStringMessages().leaderboardGroups(), Permission.MANAGE_LEADERBOARD_GROUPS); regattasDisplayers.add(leaderboardGroupConfigPanel); leaderboardGroupsDisplayers.add(leaderboardGroupConfigPanel); leaderboardsDisplayers.add(leaderboardGroupConfigPanel); @@ -111,12 +112,12 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements /* RACES */ final TabLayoutPanel racesTabPanel = panel.addVerticalTab(getStringMessages().races(), - "RacesPanel", SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + "RacesPanel", Permission.MANAGE_TRACKED_RACES); TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(sailingService, this, this, getStringMessages()); trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(trackedRacesManagementPanel), - getStringMessages().trackedRaces(), SailingAdminConsoleFeatures.SHOW_TRACKED_RACES); + getStringMessages().trackedRaces(), Permission.SHOW_TRACKED_RACES); regattasDisplayers.add(trackedRacesManagementPanel); final CompetitorPanel competitorPanel = new CompetitorPanel(sailingService, getStringMessages(), this); @@ -125,17 +126,17 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { getWidget().refreshCompetitorList(); } - }, getStringMessages().competitors(), SailingAdminConsoleFeatures.MANAGE_ALL_COMPETITORS); + }, getStringMessages().competitors(), Permission.MANAGE_ALL_COMPETITORS); RaceCourseManagementPanel raceCourseManagementPanel = new RaceCourseManagementPanel(sailingService, this, this, getStringMessages()); - panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(raceCourseManagementPanel), getStringMessages().courseLayout(), SailingAdminConsoleFeatures.MANAGE_COURSE_LAYOUT); + panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(raceCourseManagementPanel), getStringMessages().courseLayout(), Permission.MANAGE_COURSE_LAYOUT); regattasDisplayers.add(raceCourseManagementPanel); final AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); WindPanel windPanel = new WindPanel(sailingService, asyncActionsExecutor, this, this, getStringMessages()); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(windPanel), getStringMessages().wind(), - SailingAdminConsoleFeatures.MANAGE_WIND); + Permission.MANAGE_WIND); regattasDisplayers.add(windPanel); final MediaPanel mediaPanel = new MediaPanel(regattasDisplayers, sailingService, this, mediaService, this, getStringMessages()); @@ -144,29 +145,29 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { getWidget().onShow(); } - }, getStringMessages().mediaPanel(), SailingAdminConsoleFeatures.MANAGE_MEDIA); + }, getStringMessages().mediaPanel(), Permission.MANAGE_MEDIA); /* CONNECTORS */ final TabLayoutPanel connectorsTabPanel = panel.addVerticalTab(getStringMessages().connectors(), - "TrackingProviderPanel", SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + "TrackingProviderPanel", Permission.MANAGE_TRACKED_RACES); TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(sailingService, this, this, getStringMessages()); tractracEventManagementPanel.ensureDebugId("TracTracEventManagement"); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(tractracEventManagementPanel), - getStringMessages().tracTracEvents(), SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + getStringMessages().tracTracEvents(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(tractracEventManagementPanel); SwissTimingReplayConnectorPanel swissTimingReplayConnectorPanel = new SwissTimingReplayConnectorPanel( sailingService, this, this, getStringMessages()); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(swissTimingReplayConnectorPanel), - getStringMessages().swissTimingArchiveConnector(), SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + getStringMessages().swissTimingArchiveConnector(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(swissTimingReplayConnectorPanel); SwissTimingEventManagementPanel swisstimingEventManagementPanel = new SwissTimingEventManagementPanel( sailingService, this, this, getStringMessages()); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(swisstimingEventManagementPanel), - getStringMessages().swissTimingEvents(), SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + getStringMessages().swissTimingEvents(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(swisstimingEventManagementPanel); final RaceLogTrackingEventManagementPanel raceLogTrackingEventManagementPanel = new RaceLogTrackingEventManagementPanel( @@ -176,57 +177,57 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements public void refreshAfterBecomingVisible() { fillLeaderboards(); } - }, getStringMessages().raceLogTracking(), SailingAdminConsoleFeatures.MANAGE_TRACKED_RACES); + }, getStringMessages().raceLogTracking(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(raceLogTrackingEventManagementPanel); leaderboardsDisplayers.add(raceLogTrackingEventManagementPanel); IgtimiAccountsPanel igtimiAccountsPanel = new IgtimiAccountsPanel(sailingService, this, getStringMessages()); igtimiAccountsPanel.ensureDebugId("IgtimiAccounts"); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(igtimiAccountsPanel), - getStringMessages().igtimiAccounts(), SailingAdminConsoleFeatures.MANAGE_IGTIMI_ACCOUNTS); + getStringMessages().igtimiAccounts(), Permission.MANAGE_IGTIMI_ACCOUNTS); ResultImportUrlsManagementPanel resultImportUrlsManagementPanel = new ResultImportUrlsManagementPanel(sailingService, this, getStringMessages()); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(resultImportUrlsManagementPanel), - getStringMessages().resultImportUrls(), SailingAdminConsoleFeatures.MANAGE_RESULT_IMPORT_URLS); + getStringMessages().resultImportUrls(), Permission.MANAGE_RESULT_IMPORT_URLS); StructureImportManagementPanel structureImportUrlsManagementPanel = new StructureImportManagementPanel(sailingService, this, getStringMessages(), this, eventManagementPanel); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(structureImportUrlsManagementPanel), - getStringMessages().structureImportUrls(), SailingAdminConsoleFeatures.MANAGE_STRUCTURE_IMPORT_URLS); + getStringMessages().structureImportUrls(), Permission.MANAGE_STRUCTURE_IMPORT_URLS); /* ADVANCED */ final TabLayoutPanel advancedTabPanel = panel.addVerticalTab(getStringMessages().advanced(), - "AdvancedPanel", SailingAdminConsoleFeatures.MANAGE_REPLICATION); + "AdvancedPanel", Permission.MANAGE_REPLICATION); final ReplicationPanel replicationPanel = new ReplicationPanel(sailingService, this, getStringMessages()); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(replicationPanel) { @Override public void refreshAfterBecomingVisible() { replicationPanel.updateReplicaList(); } - }, getStringMessages().replication(), SailingAdminConsoleFeatures.MANAGE_REPLICATION); + }, getStringMessages().replication(), Permission.MANAGE_REPLICATION); final MasterDataImportPanel masterDataImportPanel = new MasterDataImportPanel(getStringMessages(), sailingService, this, eventManagementPanel, this, this); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(masterDataImportPanel), - getStringMessages().masterDataImportPanel(), SailingAdminConsoleFeatures.MANAGE_MASTERDATA_IMPORT); + getStringMessages().masterDataImportPanel(), Permission.MANAGE_MASTERDATA_IMPORT); RemoteSailingServerInstancesManagementPanel sailingServerInstancesManagementPanel = new RemoteSailingServerInstancesManagementPanel(sailingService, this, getStringMessages()); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(sailingServerInstancesManagementPanel), - getStringMessages().sailingServers(), SailingAdminConsoleFeatures.MANAGE_SAILING_SERVER_INSTANCES); + getStringMessages().sailingServers(), Permission.MANAGE_SAILING_SERVER_INSTANCES); final DeviceConfigurationUserPanel deviceConfigurationUserPanel = new DeviceConfigurationUserPanel(sailingService, getStringMessages(), this); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(deviceConfigurationUserPanel), - getStringMessages().deviceConfiguration(), SailingAdminConsoleFeatures.MANAGE_DEVICE_CONFIGURATION); + getStringMessages().deviceConfiguration(), Permission.MANAGE_DEVICE_CONFIGURATION); final UserManagementPanel userManagementPanel = new UserManagementPanel(getUserService(), StringMessages.INSTANCE, SailingPermissionsForRoleProvider.INSTANCE); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(userManagementPanel), - getStringMessages().userManagement(), SailingAdminConsoleFeatures.MANAGE_USERS); + getStringMessages().userManagement(), Permission.MANAGE_USERS); final FileStoragePanel fileStoragePanel = new FileStoragePanel(sailingService, this); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(fileStoragePanel), - getStringMessages().fileStorage(), SailingAdminConsoleFeatures.MANAGE_FILE_STORAGE); + getStringMessages().fileStorage(), Permission.MANAGE_FILE_STORAGE); panel.initUI(); fillRegattas(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java deleted file mode 100644 index 48183385ea4..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SailingAdminConsoleFeatures.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import com.sap.sailing.gwt.ui.client.shared.security.Permission; -import com.sap.sse.gwt.adminconsole.AdminConsoleFeatures; - -public interface SailingAdminConsoleFeatures { - final AdminConsoleFeatures MANAGE_EVENTS = new AdminConsoleFeatures(Permission.MANAGE_EVENTS); - final AdminConsoleFeatures MANAGE_REGATTAS = new AdminConsoleFeatures(Permission.MANAGE_REGATTAS); - final AdminConsoleFeatures MANAGE_TRACKED_RACES = new AdminConsoleFeatures(Permission.MANAGE_TRACKED_RACES); - final AdminConsoleFeatures SHOW_TRACKED_RACES = new AdminConsoleFeatures(Permission.SHOW_TRACKED_RACES); - final AdminConsoleFeatures MANAGE_RACELOG_TRACKING = new AdminConsoleFeatures(Permission.MANAGE_RACELOG_TRACKING); - final AdminConsoleFeatures MANAGE_ALL_COMPETITORS = new AdminConsoleFeatures(Permission.MANAGE_ALL_COMPETITORS); - final AdminConsoleFeatures MANAGE_COURSE_LAYOUT = new AdminConsoleFeatures(Permission.MANAGE_COURSE_LAYOUT); - final AdminConsoleFeatures MANAGE_WIND = new AdminConsoleFeatures(Permission.MANAGE_WIND); - final AdminConsoleFeatures MANAGE_IGTIMI_ACCOUNTS = new AdminConsoleFeatures(Permission.MANAGE_IGTIMI_ACCOUNTS); - final AdminConsoleFeatures MANAGE_LEADERBOARDS = new AdminConsoleFeatures(Permission.MANAGE_LEADERBOARDS); - final AdminConsoleFeatures MANAGE_LEADERBOARD_GROUPS = new AdminConsoleFeatures(Permission.MANAGE_LEADERBOARD_GROUPS); - final AdminConsoleFeatures MANAGE_RESULT_IMPORT_URLS = new AdminConsoleFeatures(Permission.MANAGE_RESULT_IMPORT_URLS); - final AdminConsoleFeatures MANAGE_STRUCTURE_IMPORT_URLS = new AdminConsoleFeatures(Permission.MANAGE_STRUCTURE_IMPORT_URLS); - final AdminConsoleFeatures MANAGE_MEDIA = new AdminConsoleFeatures(Permission.MANAGE_MEDIA); - final AdminConsoleFeatures MANAGE_SAILING_SERVER_INSTANCES = new AdminConsoleFeatures(Permission.MANAGE_SAILING_SERVER_INSTANCES); - final AdminConsoleFeatures MANAGE_REPLICATION = new AdminConsoleFeatures(Permission.MANAGE_REPLICATION); - final AdminConsoleFeatures MANAGE_MASTERDATA_IMPORT = new AdminConsoleFeatures(Permission.MANAGE_MASTERDATA_IMPORT); - final AdminConsoleFeatures MANAGE_DEVICE_CONFIGURATION = new AdminConsoleFeatures(Permission.MANAGE_DEVICE_CONFIGURATION); - final AdminConsoleFeatures MANAGE_USERS = new AdminConsoleFeatures(Permission.MANAGE_USERS); - final AdminConsoleFeatures MANAGE_FILE_STORAGE = new AdminConsoleFeatures(Permission.MANAGE_FILE_STORAGE); -} diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java index 0bc85c68f58..d9a67198703 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java @@ -20,6 +20,7 @@ import com.sap.sse.gwt.client.AbstractEntryPoint; import com.sap.sse.gwt.client.BuildVersionRetriever; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.panels.VerticalTabLayoutPanel; +import com.sap.sse.security.shared.Permission; import com.sap.sse.security.shared.PermissionsForRoleProvider; import com.sap.sse.security.ui.client.UserService; import com.sap.sse.security.ui.client.UserStatusEventHandler; @@ -63,7 +64,7 @@ public class AdminConsolePanel extends DockLayoutPanel { * tabs. {@link AdminConsoleFeatures} list the roles to which they are made available. This map keeps track of the * dependencies and allows the UI to adjust to role changes. */ - private final LinkedHashMap, AdminConsoleFeatures> roleSpecificTabs; + private final LinkedHashMap, Permission> roleSpecificTabs; private final SelectionHandler tabSelectionHandler; @@ -209,7 +210,7 @@ public class AdminConsolePanel extends DockLayoutPanel { * @return the horizontal tab panel that was created and added to the top-level vertical tab panel; the panel returned can be specified * as argument to {@link #addToTabPanel(TabLayoutPanel, Widget, String, AdminConsoleFeatures)}. */ - public TabLayoutPanel addVerticalTab(String tabTitle, String tabDebugId, AdminConsoleFeatures feature) { + public TabLayoutPanel addVerticalTab(String tabTitle, String tabDebugId, Permission feature) { final TabLayoutPanel newTabPanel = new TabLayoutPanel(2.5, Unit.EM); AbstractEntryPoint.setTabPanelSize(newTabPanel, "100%", "100%"); newTabPanel.addSelectionHandler(tabSelectionHandler); @@ -223,7 +224,7 @@ public class AdminConsolePanel extends DockLayoutPanel { * This is useful for panels that form a top-level category of its own but don't require multiple panels to represent this * top-level category. */ - public void addToVerticalTabPanel(final RefreshableAdminConsolePanel panelToAdd, String tabTitle, AdminConsoleFeatures feature) { + public void addToVerticalTabPanel(final RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission feature) { addToTabPanel(topLevelTabPanelWrapper, panelToAdd, tabTitle, feature); } @@ -234,7 +235,7 @@ public class AdminConsolePanel extends DockLayoutPanel { return scrollPanel; } - public void addToTabPanel(final TabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, AdminConsoleFeatures feature) { + public void addToTabPanel(final TabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission feature) { VerticalOrHorizontalTabLayoutPanel wrapper = new VerticalOrHorizontalTabLayoutPanel() { @Override public void add(Widget child, String text, boolean asHtml) { @@ -255,7 +256,7 @@ public class AdminConsolePanel extends DockLayoutPanel { * a hook so that when the panelToAdd's widget is selected then the {@link RefreshableAdminConsolePanel#refreshAfterBecomingVisible()} * method can be called. */ - private void addToTabPanel(VerticalOrHorizontalTabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, AdminConsoleFeatures feature) { + private void addToTabPanel(VerticalOrHorizontalTabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission feature) { remeberWidgetLocationAndFeature(tabPanel, wrapInScrollPanel(panelToAdd.getWidget()), tabTitle, feature); panelsByWidget.put(panelToAdd.getWidget(), panelToAdd); } @@ -264,7 +265,7 @@ public class AdminConsolePanel extends DockLayoutPanel { * Remembers the tab panel in which the widgetToAdd is to be displayed and for which feature. */ private void remeberWidgetLocationAndFeature(VerticalOrHorizontalTabLayoutPanel tabPanel, Widget widgetToAdd, - String tabTitle, AdminConsoleFeatures feature) { + String tabTitle, Permission feature) { roleSpecificTabs.put(new Triple(tabPanel, widgetToAdd, tabTitle), feature); } @@ -274,10 +275,10 @@ public class AdminConsolePanel extends DockLayoutPanel { * to see which tabs. See {@link #roleSpecificTabs}. */ private void updateTabDisplayForCurrentUser(UserDTO user) { - for (Map.Entry, AdminConsoleFeatures> e : roleSpecificTabs + for (Map.Entry, Permission> e : roleSpecificTabs .entrySet()) { final Widget panelToAdd = e.getKey().getB(); - if (user != null && user.hasPermission(e.getValue().getRequiredPermission().getStringPermission(), permissionsForRoleProvider)) { + if (user != null && user.hasPermission(e.getValue().getStringPermission(), permissionsForRoleProvider)) { e.getKey().getA().add(panelToAdd, e.getKey().getC(), /* asHtml */false); } else { e.getKey().getA().remove(panelToAdd); From 2f86e6c91ab035e28336e94368b9df4f3a68f05c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 18 Feb 2015 23:50:14 +0100 Subject: [PATCH 154/264] show a vertical tab as soon as the user has permissions to view at least one of the widgets contained --- .../adminconsole/AdminConsoleEntryPoint.java | 12 +- .../gwt/adminconsole/AdminConsolePanel.java | 128 ++++++++++++------ 2 files changed, 90 insertions(+), 50 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index e7f30ff1ccb..2b516915b5e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -81,8 +81,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements /* LEADERBOARDS */ - final TabLayoutPanel leaderboardTabPanel = panel.addVerticalTab(getStringMessages().leaderboards(), - "LeaderboardPanel", Permission.MANAGE_LEADERBOARDS); + final TabLayoutPanel leaderboardTabPanel = panel.addVerticalTab(getStringMessages().leaderboards(), "LeaderboardPanel"); final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, getStringMessages(), /* showRaceDetails */true, this); leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration"); @@ -111,8 +110,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements /* RACES */ - final TabLayoutPanel racesTabPanel = panel.addVerticalTab(getStringMessages().races(), - "RacesPanel", Permission.MANAGE_TRACKED_RACES); + final TabLayoutPanel racesTabPanel = panel.addVerticalTab(getStringMessages().races(), "RacesPanel"); TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(sailingService, this, this, getStringMessages()); trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); @@ -149,8 +147,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements /* CONNECTORS */ - final TabLayoutPanel connectorsTabPanel = panel.addVerticalTab(getStringMessages().connectors(), - "TrackingProviderPanel", Permission.MANAGE_TRACKED_RACES); + final TabLayoutPanel connectorsTabPanel = panel.addVerticalTab(getStringMessages().connectors(), "TrackingProviderPanel"); TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(sailingService, this, this, getStringMessages()); tractracEventManagementPanel.ensureDebugId("TracTracEventManagement"); @@ -196,8 +193,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements /* ADVANCED */ - final TabLayoutPanel advancedTabPanel = panel.addVerticalTab(getStringMessages().advanced(), - "AdvancedPanel", Permission.MANAGE_REPLICATION); + final TabLayoutPanel advancedTabPanel = panel.addVerticalTab(getStringMessages().advanced(), "AdvancedPanel"); final ReplicationPanel replicationPanel = new ReplicationPanel(sailingService, this, getStringMessages()); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(replicationPanel) { @Override diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java index d9a67198703..ff67a2bd38d 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java @@ -1,8 +1,11 @@ package com.sap.sse.gwt.adminconsole; +import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedHashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.logical.shared.SelectionEvent; @@ -29,29 +32,35 @@ import com.sap.sse.security.ui.loginpanel.LoginPanelCss; import com.sap.sse.security.ui.shared.UserDTO; /** - * A panel that can be used to implement an administration console. Widgets can be arranged in vertical and horizontal tabs ("L-shape"). - * The top-level element is the vertical tab panel. Widgets may either be added directly as the content of one vertical tab, or a horizontal - * tab panel can be added as the content widget of a vertical tab, in turn holding widgets in horizontal tabs.

    + * A panel that can be used to implement an administration console. Widgets can be arranged in vertical and horizontal + * tabs ("L-shape"). The top-level element is the vertical tab panel. Widgets may either be added directly as the + * content of one vertical tab, or a horizontal tab panel can be added as the content widget of a vertical tab, in turn + * holding widgets in horizontal tabs. + *

    * - * After constructing an instance of this class, there are three ways for adding widgets:

      - *
    • {@link #addToVerticalTabPanel(RefreshableAdminConsolePanel, String, AdminConsoleFeatures)} adds a widget as a content element - * of a vertical tab
    • - *
    • {@link #addVerticalTab(String, String, AdminConsoleFeatures)} creates a horizontal tab panel and adds it as a content element of - * a vertical tab
    • - *
    • {@link #addToTabPanel(TabLayoutPanel, RefreshableAdminConsolePanel, String, AdminConsoleFeatures)} adds a widget as a content element - * of a horizontal tab
    • + * After constructing an instance of this class, there are three ways for adding widgets: + *
        + *
      • {@link #addToVerticalTabPanel(RefreshableAdminConsolePanel, String, Permission)} adds a widget as a content + * element of a vertical tab
      • + *
      • {@link #addVerticalTab(String, String, Permission)} creates a horizontal tab panel and adds it as a content + * element of a vertical tab
      • + *
      • {@link #addToTabPanel(TabLayoutPanel, RefreshableAdminConsolePanel, String, Permission)} adds a widget as a + * content element of a horizontal tab
      • *
      * - * Widgets to be added need to be wrapped as {@link RefreshableAdminConsolePanel} holding the widget and receiving the refresh call when - * the widget is shown because the user has selected the tab. If the component doesn't require any refresh logic, an instance of - * {@link DefaultRefreshableAdminConsolePanel} can be used to wrap the widget.

      + * Widgets to be added need to be wrapped as {@link RefreshableAdminConsolePanel} holding the widget and receiving the + * refresh call when the widget is shown because the user has selected the tab. If the component doesn't require any + * refresh logic, an instance of {@link DefaultRefreshableAdminConsolePanel} can be used to wrap the widget. + *

      * - * After the widgets have been added, {@link #initUI()} must be called to assemble all tabs for the current user's roles. The {@link #initUI()} - * method must be called each time more widgets have been added dynamically.

      + * After the widgets have been added, {@link #initUI()} must be called to assemble all tabs for the current user's + * roles. The {@link #initUI()} method must be called each time more widgets have been added dynamically. + *

      * - * For each widget added, a {@link AdminConsoleFeatures feature} needs to be specified. The feature tells the roles of which the logged-in user - * needs to have at least one in order to see the tab. When the user changes or has his/her roles updated the set of tabs visible will be - * adjusted according to the new roles available for the logged-in user.

      + * For each widget added, a {@link Permission set of permissions} needs to be specified, any of which is sufficient to + * get to see the widget. When the user changes or has his/her permissions updated the set of tabs visible will be + * adjusted according to the new roles available for the logged-in user. + *

      * * @author Axel Uhl (D043530) * @@ -61,10 +70,14 @@ public class AdminConsolePanel extends DockLayoutPanel { /** * The administration console's UI depends on the user's roles. When the roles change then so shall the display of - * tabs. {@link AdminConsoleFeatures} list the roles to which they are made available. This map keeps track of the - * dependencies and allows the UI to adjust to role changes. + * tabs. Required {@link Permission}s tell when they are to be made available based on the user's actual + * permissions. This map keeps track of the dependencies and allows the UI to adjust to role changes.

      + * + * The values are the permissions, at least one of which is required from the user to be able to see the widget. */ - private final LinkedHashMap, Permission> roleSpecificTabs; + private final LinkedHashSet> roleSpecificTabs; + + private final Map> permissionsAnyOfWhichIsRequiredToSeeWidget; private final SelectionHandler tabSelectionHandler; @@ -139,8 +152,9 @@ public class AdminConsolePanel extends DockLayoutPanel { String releaseNotesURL, ErrorReporter errorReporter, LoginPanelCss loginPanelCss) { super(Unit.EM); this.permissionsForRoleProvider = permissionsForRoleProvider; + this.permissionsAnyOfWhichIsRequiredToSeeWidget = new HashMap<>(); this.userService = userService; - roleSpecificTabs = new LinkedHashMap<>(); + roleSpecificTabs = new LinkedHashSet<>(); this.panelsByWidget = new HashMap<>(); getUserService().addUserStatusEventHandler(new UserStatusEventHandler() { @Override @@ -163,6 +177,11 @@ public class AdminConsolePanel extends DockLayoutPanel { public void remove(Widget child) { topLevelTabPanel.remove(child); } + + @Override + public Widget getPanel() { + return topLevelTabPanel; + } }; final DockPanel informationPanel = new DockPanel(); informationPanel.setSize("100%", "95%"); @@ -202,6 +221,8 @@ public class AdminConsolePanel extends DockLayoutPanel { void add(Widget child, String text, boolean asHtml); void remove(Widget child); + + Widget getPanel(); } /** @@ -210,12 +231,12 @@ public class AdminConsolePanel extends DockLayoutPanel { * @return the horizontal tab panel that was created and added to the top-level vertical tab panel; the panel returned can be specified * as argument to {@link #addToTabPanel(TabLayoutPanel, Widget, String, AdminConsoleFeatures)}. */ - public TabLayoutPanel addVerticalTab(String tabTitle, String tabDebugId, Permission feature) { + public TabLayoutPanel addVerticalTab(String tabTitle, String tabDebugId, Permission... requiresAnyOfThesePermissions) { final TabLayoutPanel newTabPanel = new TabLayoutPanel(2.5, Unit.EM); AbstractEntryPoint.setTabPanelSize(newTabPanel, "100%", "100%"); newTabPanel.addSelectionHandler(tabSelectionHandler); newTabPanel.ensureDebugId(tabDebugId); - remeberWidgetLocationAndFeature(topLevelTabPanelWrapper, newTabPanel, tabTitle, feature); + remeberWidgetLocationAndPermissions(topLevelTabPanelWrapper, newTabPanel, tabTitle, requiresAnyOfThesePermissions); return newTabPanel; } @@ -224,8 +245,8 @@ public class AdminConsolePanel extends DockLayoutPanel { * This is useful for panels that form a top-level category of its own but don't require multiple panels to represent this * top-level category. */ - public void addToVerticalTabPanel(final RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission feature) { - addToTabPanel(topLevelTabPanelWrapper, panelToAdd, tabTitle, feature); + public void addToVerticalTabPanel(final RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission... requiresAnyOfThesePermissions) { + addToTabPanel(topLevelTabPanelWrapper, panelToAdd, tabTitle, requiresAnyOfThesePermissions); } private ScrollPanel wrapInScrollPanel(Widget panelToAdd) { @@ -235,7 +256,7 @@ public class AdminConsolePanel extends DockLayoutPanel { return scrollPanel; } - public void addToTabPanel(final TabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission feature) { + public void addToTabPanel(final TabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission... requiresAnyOfThesePermissions) { VerticalOrHorizontalTabLayoutPanel wrapper = new VerticalOrHorizontalTabLayoutPanel() { @Override public void add(Widget child, String text, boolean asHtml) { @@ -247,8 +268,13 @@ public class AdminConsolePanel extends DockLayoutPanel { public void remove(Widget child) { tabPanel.remove(child); } + + @Override + public Widget getPanel() { + return tabPanel; + } }; - addToTabPanel(wrapper, panelToAdd, tabTitle, feature); + addToTabPanel(wrapper, panelToAdd, tabTitle, requiresAnyOfThesePermissions); } /** @@ -256,18 +282,28 @@ public class AdminConsolePanel extends DockLayoutPanel { * a hook so that when the panelToAdd's widget is selected then the {@link RefreshableAdminConsolePanel#refreshAfterBecomingVisible()} * method can be called. */ - private void addToTabPanel(VerticalOrHorizontalTabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission feature) { - remeberWidgetLocationAndFeature(tabPanel, wrapInScrollPanel(panelToAdd.getWidget()), tabTitle, feature); + private void addToTabPanel(VerticalOrHorizontalTabLayoutPanel tabPanel, RefreshableAdminConsolePanel panelToAdd, String tabTitle, Permission... requiresAnyOfThesePermissions) { + remeberWidgetLocationAndPermissions(tabPanel, wrapInScrollPanel(panelToAdd.getWidget()), tabTitle, requiresAnyOfThesePermissions); panelsByWidget.put(panelToAdd.getWidget(), panelToAdd); } /** - * Remembers the tab panel in which the widgetToAdd is to be displayed and for which feature. + * Remembers the tab panel in which the widgetToAdd is to be displayed and which + * permissions are sufficient to see the widget. For the tabPanel, all permissions provided here + * are added to the tab panel's permissions so that the user will see the tab panel as soon as the user may see + * any of the widgets inside that panel */ - private void remeberWidgetLocationAndFeature(VerticalOrHorizontalTabLayoutPanel tabPanel, Widget widgetToAdd, - String tabTitle, Permission feature) { - roleSpecificTabs.put(new Triple(tabPanel, - widgetToAdd, tabTitle), feature); + private void remeberWidgetLocationAndPermissions(VerticalOrHorizontalTabLayoutPanel tabPanel, Widget widgetToAdd, + String tabTitle, Permission... requiresAnyOfThesePermissions) { + roleSpecificTabs.add(new Triple(tabPanel, widgetToAdd, tabTitle)); + final HashSet permissionsAsSet = new HashSet<>(Arrays.asList(requiresAnyOfThesePermissions)); + permissionsAnyOfWhichIsRequiredToSeeWidget.put(widgetToAdd, permissionsAsSet); + Set permissionsForTabPanel = permissionsAnyOfWhichIsRequiredToSeeWidget.get(tabPanel.getPanel()); + if (permissionsForTabPanel == null) { + permissionsForTabPanel = new HashSet<>(); + permissionsAnyOfWhichIsRequiredToSeeWidget.put(tabPanel.getPanel(), permissionsForTabPanel); + } + permissionsForTabPanel.addAll(permissionsAsSet); } /** @@ -275,14 +311,22 @@ public class AdminConsolePanel extends DockLayoutPanel { * to see which tabs. See {@link #roleSpecificTabs}. */ private void updateTabDisplayForCurrentUser(UserDTO user) { - for (Map.Entry, Permission> e : roleSpecificTabs - .entrySet()) { - final Widget panelToAdd = e.getKey().getB(); - if (user != null && user.hasPermission(e.getValue().getStringPermission(), permissionsForRoleProvider)) { - e.getKey().getA().add(panelToAdd, e.getKey().getC(), /* asHtml */false); + for (Triple e : roleSpecificTabs) { + final Widget widgetToAdd = e.getB(); + if (user != null && userHasPermissionsToSeeWidget(user, e.getB())) { + e.getA().add(widgetToAdd, e.getC(), /* asHtml */false); } else { - e.getKey().getA().remove(panelToAdd); + e.getA().remove(widgetToAdd); } } } + + private boolean userHasPermissionsToSeeWidget(UserDTO user, Widget widget) { + for (Permission requiredPermission : permissionsAnyOfWhichIsRequiredToSeeWidget.get(widget)) { + if (user.hasPermission(requiredPermission.getStringPermission(), permissionsForRoleProvider)) { + return true; + } + } + return false; + } } From 412735ebbef4a8096022c3bb9f4f1759b5a8c28b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 18 Feb 2015 23:57:04 +0100 Subject: [PATCH 155/264] removed the AdminConsoleFeatures class which is no longer required --- .../adminconsole/AdminConsoleFeatures.java | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100755 java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java deleted file mode 100755 index 05081d3890a..00000000000 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsoleFeatures.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sse.gwt.adminconsole; - -import com.sap.sse.security.shared.Permission; - - -/** - * Specifies a feature with a name and the role names, one of which is required in order to be allowed - * to see and use the feature. - * - * @author Axel Uhl (D043530) - * - */ -public class AdminConsoleFeatures { - private Permission requiredPermission; - - public AdminConsoleFeatures(Permission requiredPermission) { - this.requiredPermission = requiredPermission; - } - - /** - * If the user has at least one of these roles, he/she is permitted to use this feature. - */ - public Permission getRequiredPermission() { - return requiredPermission; - } -} From 25726e99e6eecabf00932f0c37aba285d8724313 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 19 Feb 2015 01:15:09 +0100 Subject: [PATCH 156/264] trying to make gwt.test tests compile by separating out gwt-dev.jar into separate OSGi bundle, requiring its packages as needed in gwt.osgi bundle --- java/com.google.gwt.dev/.classpath | 7 + java/com.google.gwt.dev/.project | 28 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../.settings/org.eclipse.pde.core.prefs | 3 + java/com.google.gwt.dev/META-INF/MANIFEST.MF | 523 ++++++++++++++++++ java/com.google.gwt.dev/build.properties | 3 + .../lib/gwt-dev.jar | Bin java/com.google.gwt.dev/pom.xml | 13 + java/com.google.gwt.osgi/META-INF/MANIFEST.MF | 22 +- .../com.sap.sse.gwt.test/META-INF/MANIFEST.MF | 3 +- java/com.sap.sse.gwt.test/pom.xml | 8 + .../busyindicator/BusyIndicatorResources.java | 10 + .../busyindicator/SimpleBusyIndicator.java | 44 ++ .../controls/carousel/ImageCarousel.java | 267 +++++++++ java/pom.xml | 5 +- 15 files changed, 937 insertions(+), 6 deletions(-) create mode 100755 java/com.google.gwt.dev/.classpath create mode 100755 java/com.google.gwt.dev/.project create mode 100755 java/com.google.gwt.dev/.settings/org.eclipse.jdt.core.prefs create mode 100755 java/com.google.gwt.dev/.settings/org.eclipse.pde.core.prefs create mode 100755 java/com.google.gwt.dev/META-INF/MANIFEST.MF create mode 100755 java/com.google.gwt.dev/build.properties rename java/{com.google.gwt.osgi => com.google.gwt.dev}/lib/gwt-dev.jar (100%) create mode 100755 java/com.google.gwt.dev/pom.xml create mode 100644 java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java create mode 100644 java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java create mode 100644 java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/carousel/ImageCarousel.java diff --git a/java/com.google.gwt.dev/.classpath b/java/com.google.gwt.dev/.classpath new file mode 100755 index 00000000000..d7a0cfed444 --- /dev/null +++ b/java/com.google.gwt.dev/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.google.gwt.dev/.project b/java/com.google.gwt.dev/.project new file mode 100755 index 00000000000..b4df77e1e2d --- /dev/null +++ b/java/com.google.gwt.dev/.project @@ -0,0 +1,28 @@ + + + com.google.gwt.dev + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.google.gwt.dev/.settings/org.eclipse.jdt.core.prefs b/java/com.google.gwt.dev/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..295926d9641 --- /dev/null +++ b/java/com.google.gwt.dev/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.google.gwt.dev/.settings/org.eclipse.pde.core.prefs b/java/com.google.gwt.dev/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..b7e72d019c2 --- /dev/null +++ b/java/com.google.gwt.dev/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.google.gwt.dev/META-INF/MANIFEST.MF b/java/com.google.gwt.dev/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..3dace3e8b85 --- /dev/null +++ b/java/com.google.gwt.dev/META-INF/MANIFEST.MF @@ -0,0 +1,523 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Dev +Bundle-SymbolicName: com.google.gwt.dev +Bundle-Version: 2.7.0 +Bundle-Vendor: GOOGLE +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-ClassPath: lib/gwt-dev.jar, + . +Export-Package: com.gargoylesoftware.htmlunit, + com.gargoylesoftware.htmlunit.attachment, + com.gargoylesoftware.htmlunit.gae, + com.gargoylesoftware.htmlunit.html, + com.gargoylesoftware.htmlunit.html.applets, + com.gargoylesoftware.htmlunit.html.impl, + com.gargoylesoftware.htmlunit.html.xpath, + com.gargoylesoftware.htmlunit.javascript, + com.gargoylesoftware.htmlunit.javascript.background, + com.gargoylesoftware.htmlunit.javascript.configuration, + com.gargoylesoftware.htmlunit.javascript.host, + com.gargoylesoftware.htmlunit.javascript.host.arrays, + com.gargoylesoftware.htmlunit.javascript.host.canvas, + com.gargoylesoftware.htmlunit.javascript.host.css, + com.gargoylesoftware.htmlunit.javascript.host.dom, + com.gargoylesoftware.htmlunit.javascript.host.geo, + com.gargoylesoftware.htmlunit.javascript.host.html, + com.gargoylesoftware.htmlunit.javascript.host.svg, + com.gargoylesoftware.htmlunit.javascript.host.xml, + com.gargoylesoftware.htmlunit.javascript.regexp, + com.gargoylesoftware.htmlunit.protocol.about, + com.gargoylesoftware.htmlunit.protocol.data, + com.gargoylesoftware.htmlunit.protocol.javascript, + com.gargoylesoftware.htmlunit.svg, + com.gargoylesoftware.htmlunit.util, + com.gargoylesoftware.htmlunit.xml, + com.google.gwt.core.client, + com.google.gwt.core.client.impl, + com.google.gwt.core.ext, + com.google.gwt.core.ext.debug, + com.google.gwt.core.ext.impl, + com.google.gwt.core.ext.linker, + com.google.gwt.core.ext.linker.impl, + com.google.gwt.core.ext.soyc, + com.google.gwt.core.ext.soyc.coderef, + com.google.gwt.core.ext.soyc.impl, + com.google.gwt.core.ext.typeinfo, + com.google.gwt.core.linker, + com.google.gwt.core.shared, + com.google.gwt.dev, + com.google.gwt.dev.cfg, + com.google.gwt.dev.codeserver, + com.google.gwt.dev.generator, + com.google.gwt.dev.generator.ast, + com.google.gwt.dev.javac, + com.google.gwt.dev.javac.asm, + com.google.gwt.dev.javac.asmbridge, + com.google.gwt.dev.javac.testing, + com.google.gwt.dev.javac.testing.impl, + com.google.gwt.dev.javac.typemodel, + com.google.gwt.dev.jdt, + com.google.gwt.dev.jjs, + com.google.gwt.dev.jjs.ast, + com.google.gwt.dev.jjs.ast.js, + com.google.gwt.dev.jjs.impl, + com.google.gwt.dev.jjs.impl.codesplitter, + com.google.gwt.dev.jjs.impl.gflow, + com.google.gwt.dev.jjs.impl.gflow.cfg, + com.google.gwt.dev.jjs.impl.gflow.constants, + com.google.gwt.dev.jjs.impl.gflow.copy, + com.google.gwt.dev.jjs.impl.gflow.liveness, + com.google.gwt.dev.jjs.impl.gflow.unreachable, + com.google.gwt.dev.js, + com.google.gwt.dev.js.ast, + com.google.gwt.dev.js.rhino, + com.google.gwt.dev.json, + com.google.gwt.dev.protobuf, + com.google.gwt.dev.resource, + com.google.gwt.dev.resource.impl, + com.google.gwt.dev.shell, + com.google.gwt.dev.shell.ie, + com.google.gwt.dev.shell.jetty, + com.google.gwt.dev.shell.log, + com.google.gwt.dev.shell.remoteui, + com.google.gwt.dev.shell.rewrite, + com.google.gwt.dev.ui, + com.google.gwt.dev.url, + com.google.gwt.dev.util, + com.google.gwt.dev.util.arg, + com.google.gwt.dev.util.collect, + com.google.gwt.dev.util.editdistance, + com.google.gwt.dev.util.log, + com.google.gwt.dev.util.log.dashboard, + com.google.gwt.dev.util.log.speedtracer, + com.google.gwt.dev.util.msg, + com.google.gwt.dev.util.transitiveclosure, + com.google.gwt.dev.util.xml, + com.google.gwt.lang, + com.google.gwt.soyc, + com.google.gwt.soyc.io, + com.google.gwt.thirdparty.debugging.sourcemap, + com.google.gwt.thirdparty.debugging.sourcemap.proto, + com.google.gwt.thirdparty.guava.common.annotations, + com.google.gwt.thirdparty.guava.common.base, + com.google.gwt.thirdparty.guava.common.base.internal, + com.google.gwt.thirdparty.guava.common.cache, + com.google.gwt.thirdparty.guava.common.collect, + com.google.gwt.thirdparty.guava.common.escape, + com.google.gwt.thirdparty.guava.common.eventbus, + com.google.gwt.thirdparty.guava.common.hash, + com.google.gwt.thirdparty.guava.common.html, + com.google.gwt.thirdparty.guava.common.io, + com.google.gwt.thirdparty.guava.common.math, + com.google.gwt.thirdparty.guava.common.net, + com.google.gwt.thirdparty.guava.common.primitives, + com.google.gwt.thirdparty.guava.common.reflect, + com.google.gwt.thirdparty.guava.common.util.concurrent, + com.google.gwt.thirdparty.guava.common.xml, + com.google.gwt.thirdparty.javascript.jscomp, + com.google.gwt.thirdparty.javascript.jscomp.ant, + com.google.gwt.thirdparty.javascript.jscomp.deps, + com.google.gwt.thirdparty.javascript.jscomp.fuzzing, + com.google.gwt.thirdparty.javascript.jscomp.graph, + com.google.gwt.thirdparty.javascript.jscomp.parsing, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser.codegeneration, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser.trees, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser.util, + com.google.gwt.thirdparty.javascript.jscomp.parsing.parser.util.format, + com.google.gwt.thirdparty.javascript.jscomp.regex, + com.google.gwt.thirdparty.javascript.jscomp.type, + com.google.gwt.thirdparty.javascript.rhino, + com.google.gwt.thirdparty.javascript.rhino.head, + com.google.gwt.thirdparty.javascript.rhino.head.annotations, + com.google.gwt.thirdparty.javascript.rhino.head.ast, + com.google.gwt.thirdparty.javascript.rhino.head.commonjs.module, + com.google.gwt.thirdparty.javascript.rhino.head.commonjs.module.provider, + com.google.gwt.thirdparty.javascript.rhino.head.debug, + com.google.gwt.thirdparty.javascript.rhino.head.jdk13, + com.google.gwt.thirdparty.javascript.rhino.head.jdk15, + com.google.gwt.thirdparty.javascript.rhino.head.json, + com.google.gwt.thirdparty.javascript.rhino.head.optimizer, + com.google.gwt.thirdparty.javascript.rhino.head.regexp, + com.google.gwt.thirdparty.javascript.rhino.head.serialize, + com.google.gwt.thirdparty.javascript.rhino.head.tools, + com.google.gwt.thirdparty.javascript.rhino.head.tools.debugger, + com.google.gwt.thirdparty.javascript.rhino.head.tools.debugger.treetable, + com.google.gwt.thirdparty.javascript.rhino.head.tools.idswitch, + com.google.gwt.thirdparty.javascript.rhino.head.tools.jsc, + com.google.gwt.thirdparty.javascript.rhino.head.tools.shell, + com.google.gwt.thirdparty.javascript.rhino.head.v8dtoa, + com.google.gwt.thirdparty.javascript.rhino.head.xml, + com.google.gwt.thirdparty.javascript.rhino.jstype, + com.google.gwt.thirdparty.json, + com.google.gwt.util, + com.google.gwt.util.tools, + com.google.gwt.util.tools.shared, + com.ibm.icu.impl, + com.ibm.icu.impl.data, + com.ibm.icu.impl.duration, + com.ibm.icu.impl.duration.impl, + com.ibm.icu.impl.locale, + com.ibm.icu.lang, + com.ibm.icu.math, + com.ibm.icu.text, + com.ibm.icu.util, + com.steadystate.css.dom, + com.steadystate.css.parser, + com.steadystate.css.parser.selectors, + com.steadystate.css.sac, + com.steadystate.css.userdata, + com.steadystate.css.util, + java_cup, + java_cup.anttask, + java_cup.runtime, + javax.annotation, + javax.annotation.concurrent, + javax.annotation.meta, + javax.servlet, + javax.servlet.annotation, + javax.servlet.descriptor, + javax.servlet.http, + javax.servlet.jsp, + javax.servlet.jsp.el, + javax.servlet.jsp.tagext, + javax.xml, + javax.xml.datatype, + javax.xml.namespace, + javax.xml.parsers, + javax.xml.stream, + javax.xml.stream.events, + javax.xml.stream.util, + javax.xml.transform, + javax.xml.transform.dom, + javax.xml.transform.sax, + javax.xml.transform.stax, + javax.xml.transform.stream, + javax.xml.validation, + javax.xml.xpath, + net.sourceforge.htmlunit.corejs.classfile, + net.sourceforge.htmlunit.corejs.javascript, + net.sourceforge.htmlunit.corejs.javascript.annotations, + net.sourceforge.htmlunit.corejs.javascript.ast, + net.sourceforge.htmlunit.corejs.javascript.commonjs.module, + net.sourceforge.htmlunit.corejs.javascript.commonjs.module.provider, + net.sourceforge.htmlunit.corejs.javascript.debug, + net.sourceforge.htmlunit.corejs.javascript.jdk13, + net.sourceforge.htmlunit.corejs.javascript.jdk15, + net.sourceforge.htmlunit.corejs.javascript.json, + net.sourceforge.htmlunit.corejs.javascript.optimizer, + net.sourceforge.htmlunit.corejs.javascript.regexp, + net.sourceforge.htmlunit.corejs.javascript.serialize, + net.sourceforge.htmlunit.corejs.javascript.tools, + net.sourceforge.htmlunit.corejs.javascript.tools.debugger, + net.sourceforge.htmlunit.corejs.javascript.tools.debugger.treetable, + net.sourceforge.htmlunit.corejs.javascript.tools.shell, + net.sourceforge.htmlunit.corejs.javascript.v8dtoa, + net.sourceforge.htmlunit.corejs.javascript.xml, + netscape.javascript, + org.apache.bcel, + org.apache.bcel.classfile, + org.apache.bcel.generic, + org.apache.bcel.util, + org.apache.bcel.verifier, + org.apache.bcel.verifier.exc, + org.apache.bcel.verifier.statics, + org.apache.bcel.verifier.structurals, + org.apache.commons.codec, + org.apache.commons.codec.binary, + org.apache.commons.codec.digest, + org.apache.commons.codec.language, + org.apache.commons.codec.language.bm, + org.apache.commons.codec.net, + org.apache.commons.collections, + org.apache.commons.collections.bag, + org.apache.commons.collections.bidimap, + org.apache.commons.collections.buffer, + org.apache.commons.collections.collection, + org.apache.commons.collections.comparators, + org.apache.commons.collections.functors, + org.apache.commons.collections.iterators, + org.apache.commons.collections.keyvalue, + org.apache.commons.collections.list, + org.apache.commons.collections.map, + org.apache.commons.collections.set, + org.apache.commons.el, + org.apache.commons.el.parser, + org.apache.commons.io, + org.apache.commons.io.comparator, + org.apache.commons.io.filefilter, + org.apache.commons.io.input, + org.apache.commons.io.monitor, + org.apache.commons.io.output, + org.apache.commons.lang3, + org.apache.commons.lang3.builder, + org.apache.commons.lang3.concurrent, + org.apache.commons.lang3.event, + org.apache.commons.lang3.exception, + org.apache.commons.lang3.math, + org.apache.commons.lang3.mutable, + org.apache.commons.lang3.reflect, + org.apache.commons.lang3.text, + org.apache.commons.lang3.text.translate, + org.apache.commons.lang3.time, + org.apache.commons.lang3.tuple, + org.apache.commons.logging, + org.apache.commons.logging.impl, + org.apache.html.dom, + org.apache.http, + org.apache.http.annotation, + org.apache.http.auth, + org.apache.http.auth.params, + org.apache.http.client, + org.apache.http.client.config, + org.apache.http.client.entity, + org.apache.http.client.methods, + org.apache.http.client.params, + org.apache.http.client.protocol, + org.apache.http.client.utils, + org.apache.http.concurrent, + org.apache.http.config, + org.apache.http.conn, + org.apache.http.conn.params, + org.apache.http.conn.routing, + org.apache.http.conn.scheme, + org.apache.http.conn.socket, + org.apache.http.conn.ssl, + org.apache.http.conn.util, + org.apache.http.cookie, + org.apache.http.cookie.params, + org.apache.http.entity, + org.apache.http.entity.mime, + org.apache.http.entity.mime.content, + org.apache.http.impl, + org.apache.http.impl.auth, + org.apache.http.impl.client, + org.apache.http.impl.conn, + org.apache.http.impl.conn.tsccm, + org.apache.http.impl.cookie, + org.apache.http.impl.entity, + org.apache.http.impl.execchain, + org.apache.http.impl.io, + org.apache.http.impl.pool, + org.apache.http.io, + org.apache.http.message, + org.apache.http.params, + org.apache.http.pool, + org.apache.http.protocol, + org.apache.http.util, + org.apache.james.mime4j, + org.apache.james.mime4j.codec, + org.apache.james.mime4j.descriptor, + org.apache.james.mime4j.field, + org.apache.james.mime4j.field.address, + org.apache.james.mime4j.field.address.parser, + org.apache.james.mime4j.field.contentdisposition.parser, + org.apache.james.mime4j.field.contenttype.parser, + org.apache.james.mime4j.field.datetime, + org.apache.james.mime4j.field.datetime.parser, + org.apache.james.mime4j.field.language.parser, + org.apache.james.mime4j.field.mimeversion.parser, + org.apache.james.mime4j.field.structured.parser, + org.apache.james.mime4j.io, + org.apache.james.mime4j.message, + org.apache.james.mime4j.parser, + org.apache.james.mime4j.storage, + org.apache.james.mime4j.util, + org.apache.jasper, + org.apache.jasper.compiler, + org.apache.jasper.compiler.tagplugin, + org.apache.jasper.runtime, + org.apache.jasper.security, + org.apache.jasper.servlet, + org.apache.jasper.tagplugins.jstl, + org.apache.jasper.util, + org.apache.jasper.xmlparser, + org.apache.regexp, + org.apache.tapestry.util.text, + org.apache.tools.ant, + org.apache.tools.ant.filters, + org.apache.tools.ant.filters.util, + org.apache.tools.ant.helper, + org.apache.tools.ant.input, + org.apache.tools.ant.listener, + org.apache.tools.ant.loader, + org.apache.tools.ant.taskdefs, + org.apache.tools.ant.taskdefs.compilers, + org.apache.tools.ant.taskdefs.condition, + org.apache.tools.ant.taskdefs.cvslib, + org.apache.tools.ant.taskdefs.email, + org.apache.tools.ant.taskdefs.rmic, + org.apache.tools.ant.types, + org.apache.tools.ant.types.mappers, + org.apache.tools.ant.types.resolver, + org.apache.tools.ant.types.selectors, + org.apache.tools.ant.types.selectors.modifiedselector, + org.apache.tools.ant.util, + org.apache.tools.ant.util.facade, + org.apache.tools.ant.util.regexp, + org.apache.tools.bzip2, + org.apache.tools.mail, + org.apache.tools.tar, + org.apache.tools.zip, + org.apache.wml, + org.apache.wml.dom, + org.apache.xalan, + org.apache.xalan.client, + org.apache.xalan.extensions, + org.apache.xalan.lib, + org.apache.xalan.lib.sql, + org.apache.xalan.processor, + org.apache.xalan.res, + org.apache.xalan.serialize, + org.apache.xalan.templates, + org.apache.xalan.trace, + org.apache.xalan.transformer, + org.apache.xalan.xslt, + org.apache.xalan.xsltc, + org.apache.xalan.xsltc.cmdline, + org.apache.xalan.xsltc.cmdline.getopt, + org.apache.xalan.xsltc.compiler, + org.apache.xalan.xsltc.compiler.util, + org.apache.xalan.xsltc.dom, + org.apache.xalan.xsltc.runtime, + org.apache.xalan.xsltc.runtime.output, + org.apache.xalan.xsltc.trax, + org.apache.xalan.xsltc.util, + org.apache.xerces.dom, + org.apache.xerces.dom.events, + org.apache.xerces.dom3.as, + org.apache.xerces.impl, + org.apache.xerces.impl.dtd, + org.apache.xerces.impl.dtd.models, + org.apache.xerces.impl.dv, + org.apache.xerces.impl.dv.dtd, + org.apache.xerces.impl.dv.util, + org.apache.xerces.impl.dv.xs, + org.apache.xerces.impl.io, + org.apache.xerces.impl.msg, + org.apache.xerces.impl.validation, + org.apache.xerces.impl.xpath, + org.apache.xerces.impl.xpath.regex, + org.apache.xerces.impl.xs, + org.apache.xerces.impl.xs.identity, + org.apache.xerces.impl.xs.models, + org.apache.xerces.impl.xs.opti, + org.apache.xerces.impl.xs.traversers, + org.apache.xerces.impl.xs.util, + org.apache.xerces.jaxp, + org.apache.xerces.jaxp.datatype, + org.apache.xerces.jaxp.validation, + org.apache.xerces.parsers, + org.apache.xerces.stax, + org.apache.xerces.stax.events, + org.apache.xerces.util, + org.apache.xerces.xinclude, + org.apache.xerces.xni, + org.apache.xerces.xni.grammars, + org.apache.xerces.xni.parser, + org.apache.xerces.xpointer, + org.apache.xerces.xs, + org.apache.xerces.xs.datatypes, + org.apache.xml.dtm, + org.apache.xml.dtm.ref, + org.apache.xml.dtm.ref.dom2dtm, + org.apache.xml.dtm.ref.sax2dtm, + org.apache.xml.res, + org.apache.xml.serialize, + org.apache.xml.serializer, + org.apache.xml.serializer.dom3, + org.apache.xml.serializer.utils, + org.apache.xml.utils, + org.apache.xml.utils.res, + org.apache.xmlcommons, + org.apache.xpath, + org.apache.xpath.axes, + org.apache.xpath.compiler, + org.apache.xpath.domapi, + org.apache.xpath.functions, + org.apache.xpath.jaxp, + org.apache.xpath.objects, + org.apache.xpath.operations, + org.apache.xpath.patterns, + org.apache.xpath.res, + org.cyberneko.html, + org.cyberneko.html.filters, + org.cyberneko.html.parsers, + org.cyberneko.html.xercesbridge, + org.eclipse.jdt.core, + org.eclipse.jdt.core.compiler, + org.eclipse.jdt.core.compiler.batch, + org.eclipse.jdt.internal.antadapter, + org.eclipse.jdt.internal.compiler, + org.eclipse.jdt.internal.compiler.ast, + org.eclipse.jdt.internal.compiler.batch, + org.eclipse.jdt.internal.compiler.classfmt, + org.eclipse.jdt.internal.compiler.codegen, + org.eclipse.jdt.internal.compiler.env, + org.eclipse.jdt.internal.compiler.flow, + org.eclipse.jdt.internal.compiler.impl, + org.eclipse.jdt.internal.compiler.lookup, + org.eclipse.jdt.internal.compiler.parser, + org.eclipse.jdt.internal.compiler.parser.diagnose, + org.eclipse.jdt.internal.compiler.problem, + org.eclipse.jdt.internal.compiler.util, + org.eclipse.jdt.internal.core.util, + org.eclipse.jetty.continuation, + org.eclipse.jetty.http, + org.eclipse.jetty.http.gzip, + org.eclipse.jetty.http.ssl, + org.eclipse.jetty.io, + org.eclipse.jetty.io.bio, + org.eclipse.jetty.io.nio, + org.eclipse.jetty.security, + org.eclipse.jetty.security.authentication, + org.eclipse.jetty.server, + org.eclipse.jetty.server.bio, + org.eclipse.jetty.server.handler, + org.eclipse.jetty.server.handler.jmx, + org.eclipse.jetty.server.jmx, + org.eclipse.jetty.server.nio, + org.eclipse.jetty.server.session, + org.eclipse.jetty.server.session.jmx, + org.eclipse.jetty.server.ssl, + org.eclipse.jetty.servlet, + org.eclipse.jetty.servlet.jmx, + org.eclipse.jetty.servlet.listener, + org.eclipse.jetty.servlets, + org.eclipse.jetty.util, + org.eclipse.jetty.util.ajax, + org.eclipse.jetty.util.component, + org.eclipse.jetty.util.log, + org.eclipse.jetty.util.preventers, + org.eclipse.jetty.util.resource, + org.eclipse.jetty.util.security, + org.eclipse.jetty.util.ssl, + org.eclipse.jetty.util.statistic, + org.eclipse.jetty.util.thread, + org.eclipse.jetty.webapp, + org.eclipse.jetty.websocket, + org.eclipse.jetty.xml, + org.objectweb.asm, + org.objectweb.asm.commons, + org.objectweb.asm.signature, + org.objectweb.asm.tree, + org.objectweb.asm.tree.analysis, + org.objectweb.asm.util, + org.objectweb.asm.xml, + org.w3c.css.sac, + org.w3c.css.sac.helpers, + org.w3c.dom, + org.w3c.dom.bootstrap, + org.w3c.dom.css, + org.w3c.dom.events, + org.w3c.dom.html, + org.w3c.dom.ls, + org.w3c.dom.ranges, + org.w3c.dom.stylesheets, + org.w3c.dom.traversal, + org.w3c.dom.views, + org.w3c.dom.xpath, + org.xml.sax, + org.xml.sax.ext, + org.xml.sax.helpers diff --git a/java/com.google.gwt.dev/build.properties b/java/com.google.gwt.dev/build.properties new file mode 100755 index 00000000000..2b37c8da50e --- /dev/null +++ b/java/com.google.gwt.dev/build.properties @@ -0,0 +1,3 @@ +bin.includes = META-INF/,\ + .,\ + lib/gwt-dev.jar diff --git a/java/com.google.gwt.osgi/lib/gwt-dev.jar b/java/com.google.gwt.dev/lib/gwt-dev.jar similarity index 100% rename from java/com.google.gwt.osgi/lib/gwt-dev.jar rename to java/com.google.gwt.dev/lib/gwt-dev.jar diff --git a/java/com.google.gwt.dev/pom.xml b/java/com.google.gwt.dev/pom.xml new file mode 100755 index 00000000000..845aa49059e --- /dev/null +++ b/java/com.google.gwt.dev/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.google.gwt.dev + 2.7.0 + eclipse-plugin + diff --git a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF index 8c41d90b6e2..400e003ca8c 100644 --- a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF @@ -172,6 +172,22 @@ Export-Package: com.google.gwt.activity.shared, com.google.web.bindery.requestfactory.vm.testing, org.hibernate.validator, org.hibernate.validator.engine -Require-Bundle: javax.validation;bundle-version="1.0.0" -Import-Package: javax.servlet;version="2.6.0", - javax.servlet.http;version="2.6.0" +Require-Bundle: javax.validation;bundle-version="1.0.0", + org.junit4;bundle-version="4.8.2" +Import-Package: com.gargoylesoftware.htmlunit, + com.google.gwt.core.ext, + com.google.gwt.core.ext.typeinfo, + com.google.gwt.dev, + com.google.gwt.dev.cfg, + com.google.gwt.dev.javac.typemodel, + com.google.gwt.dev.shell, + com.google.gwt.dev.shell.jetty, + com.google.gwt.dev.util.arg, + com.google.gwt.dev.util.collect, + com.google.gwt.junit, + com.google.gwt.thirdparty.guava.common.collect, + com.google.gwt.util.tools, + javax.servlet;version="2.6.0", + javax.servlet.http;version="2.6.0", + org.eclipse.jetty.server, + org.eclipse.jetty.webapp diff --git a/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF index 7fbd22023b4..4986d296d89 100644 --- a/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt.test/META-INF/MANIFEST.MF @@ -7,4 +7,5 @@ Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Require-Bundle: com.sap.sse.gwt;bundle-version="1.0.0", com.sap.sse.common;bundle-version="1.0.0", - org.junit4;bundle-version="4.8.2" + org.junit4;bundle-version="4.8.2", + com.google.gwt.osgi;bundle-version="2.7.0" diff --git a/java/com.sap.sse.gwt.test/pom.xml b/java/com.sap.sse.gwt.test/pom.xml index b22d0bb4c1c..73933277341 100644 --- a/java/com.sap.sse.gwt.test/pom.xml +++ b/java/com.sap.sse.gwt.test/pom.xml @@ -9,6 +9,14 @@ com.sap.sse.gwt.test eclipse-test-plugin + + + com.google.gwt + gwt-dev + ${gwt.version} + + + diff --git a/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java b/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java new file mode 100644 index 00000000000..1add987b8c5 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java @@ -0,0 +1,10 @@ +package com.sap.sse.gwt.client.controls.busyindicator; + +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.ImageResource; + +public interface BusyIndicatorResources extends ClientBundle { + + @Source("busy_indicator_circle.gif") + ImageResource busyIndicatorCircle(); +} diff --git a/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java b/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java new file mode 100644 index 00000000000..5a607e233d6 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java @@ -0,0 +1,44 @@ +package com.sap.sse.gwt.client.controls.busyindicator; + +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.user.client.ui.Image; + +public class SimpleBusyIndicator extends BusyIndicator { + + private Image busyIndicator; + + /** + * Creates a new SimpleBusyIndicator with the busy state false.
      + * The busy indicator component is a circling GIF. + */ + public SimpleBusyIndicator() { + this(false, 1.0f); + } + + /** + * Creates a new SimpleBusyIndicator with a custom busy state.
      + * The busy indicator component is a circling GIF. + * + * @param busy Sets the busy state of the BusyIndicator + * @param scale Scales the displayed image. 1.0 is 100%, 0.50 is 50%, ... + */ + public SimpleBusyIndicator(boolean busy, float scale) { + this(busy, scale, STYLE_NAME_PREFIX + "Simple", STYLE_NAME_PREFIX + "Circle"); + } + + public SimpleBusyIndicator(boolean busy, float scale, String panelCssClass, String imageCssClass) { + this.setStyleName(panelCssClass); + ImageResource resource = RESOURCES.busyIndicatorCircle(); + busyIndicator = new Image(resource.getSafeUri()); + busyIndicator.setStyleName(imageCssClass); + busyIndicator.setPixelSize((int) (resource.getWidth() * scale), (int) (resource.getHeight() * scale)); + add(busyIndicator); + setBusy(busy); + } + + @Override + public void setBusy(boolean busy) { + super.setBusy(busy); + busyIndicator.setVisible(busy); + } +} diff --git a/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/carousel/ImageCarousel.java b/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/carousel/ImageCarousel.java new file mode 100644 index 00000000000..a5456c8f101 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/carousel/ImageCarousel.java @@ -0,0 +1,267 @@ +package com.sap.sse.gwt.client.controls.carousel; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.safehtml.shared.UriUtils; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.Widget; + +/** + * Image carousel that uses the open source "slick carousel". + *

      + * Documentation/ download of slick js and css from homepate/ github page. + * + * @see Slick homepage. + * @see Slick github page. + * + * + *

      + * + * This class uses JSNI to wrap the JS slick carousel. + * + * Usage: + * + * - Create widget + * + * ImageCarousel slider = new ImageCarousel(); + * + * - Add images + * + * slider.addImage("http://demotivators.despair.com/demotivational/foresightdemotivator.jpg"); + * slider.addImage("http://demotivators.despair.com/demotivational/commitmentdemotivator.jpg"); + * slider.addImage("http://demotivators.despair.com/demotivational/platitudesdemotivator.jpg"); + * slider.addImage("http://demotivators.despair.com/demotivational/destinyrockdemotivator.jpg"); + * slider.addImage("http://demotivators.despair.com/demotivational/aspirationdemotivator.jpg"); + * + * - Start the carousel + * + * slider.init(); + * + * The widget depends on css and js resources, as well as jquery 1.7+. + * + * + * Created by pgtaboada on 10.11.14. + */ +public class ImageCarousel extends Widget { + + private static SlickSliderUiBinder ourUiBinder = GWT.create(SlickSliderUiBinder.class); + + /** + * slick slider property: dots + */ + private boolean showDots = true; + /** + * slick slider property: centerMode + */ + private boolean centerMode = true; + /** + * slick slider property: variableWidth + */ + private boolean variableWidth = true; + /** + * slick slider property: infiniteScrolling + */ + private boolean infiniteScrolling = true; + /** + * Image margin (effectively sets spacing between images) + */ + private int contentMarginInPixels = 10; + /** + * Lazy load mechanism to use + */ + private String lazyload = LAZYLOAD.ONDEMAND.getPropertyValue(); + + /** + * The height of the images + */ + private int imagesHeight = 300; + + private final String uniqueId; + + /** + * Widget constructor + */ + public ImageCarousel() { + setElement(ourUiBinder.createAndBindUi(this)); + + uniqueId = "slider_" + Document.get().createUniqueId(); + getElement().addClassName(uniqueId); + init(); + } + + /** + * JSNI wrapper that does setup the slider + * + * @param uniqueId + */ + native void setupSlider(ImageCarousel sliderReference) /*-{ + + $wnd + .$( + '.' + + (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::uniqueId)) + .slick( + { + dots : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::showDots), + infinite : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::infiniteScrolling), + centerMode : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::centerMode), + variableWidth : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::variableWidth), + lazyLoad : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::lazyload), + swipeToSlide : false, + arrows : true, + responsive : true, + slidesToShow : 2 + + }); + $wnd + .$( + '.' + + (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::uniqueId) + + '>.slick-list>.slick-track') + .height( + sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::imagesHeight + + 'px'); + }-*/; + + /** + * Add new image to carousel. + * + * @param url + */ + public void addImage(String url, int height, int width) { + + DivElement imageHolder = Document.get().createDivElement(); + ImageElement imageElement = Document.get().createImageElement(); + imageElement.setAttribute("data-lazy", UriUtils.fromString(url).asString()); + + imageHolder.getStyle().setHeight(imagesHeight, Unit.PX); + imageHolder.getStyle().setWidth(Math.round(width * (imagesHeight / (double) height)), Unit.PX); + imageHolder.appendChild(imageElement); + + getElement().appendChild(imageHolder); + + if (getElement().getChildCount() > 20) { + setInfiniteScrolling(false); + setShowDots(false); + } + + } + + /** + * Define spacing between images in carousel. In fact, it is setting the img margins in pixels. + * + * @param contentMarginInPixels + */ + public void setContentMarginInPixels(int contentMarginInPixels) { + this.contentMarginInPixels = contentMarginInPixels; + } + + /** + * Set lazy loading technique. Accepts 'ondemand' or 'progressive'. + * + * @param lazyload + */ + public void setLazyload(LAZYLOAD lazyload) { + this.lazyload = lazyload.getPropertyValue(); + } + + /** + * Enable infinite loop sliding + */ + public void setInfiniteScrolling(boolean infiniteScrolling) { + this.infiniteScrolling = infiniteScrolling; + } + + /** + * Enable variable width slides. + * + * @param variableWidth + */ + public void setVariableWidth(boolean variableWidth) { + this.variableWidth = variableWidth; + } + + /** + * Enables centered view with partial prev/next slides. + * + * @param centerMode + */ + public void setCenterMode(boolean centerMode) { + this.centerMode = centerMode; + } + + /** + * Enable dot indicators + * + * @param showDots + */ + public void setShowDots(boolean showDots) { + this.showDots = showDots; + } + + /** + * UiBinder interface + */ + interface SlickSliderUiBinder extends UiBinder { + } + + /** + * Enum wrapper for lazy load techniques + */ + public enum LAZYLOAD { + + /** + * Loads each image "on demand" + */ + ONDEMAND("ondemand"), + + /** + * Progressively loads one image after another + */ + PROGRESSIVE("progressive"); + + String propertyValue; + + LAZYLOAD(String propertyValue) { + this.propertyValue = propertyValue; + } + + public String getPropertyValue() { + return propertyValue; + } + } + + /** + * Apply slick js to constructed dom tree. + */ + private void init() { + final ImageCarousel reference = this; + + Scheduler.get().scheduleDeferred(new Command() { + + @Override + public void execute() { + try { + setupSlider(reference); + + for (int childIndex = 0; childIndex < getElement().getChildCount(); childIndex++) { + Element child = Element.as(getElement().getChild(childIndex)); + if (child.getClassName() != null && child.getClassName().contains("slick-track")) { + + } + } + + } catch (Exception e) { + GWT.log("Catched Exception on slider init", e); + } + } + }); + } + +} \ No newline at end of file diff --git a/java/pom.xml b/java/pom.xml index 3374be9270a..8748125cac6 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -60,6 +60,7 @@ com.sap.sailing.www com.sap.sailing.www.events com.sap.sailing.xmlexport + com.google.gwt.dev com.google.gwt.osgi com.tractrac.clientmodule org.json.simple @@ -85,9 +86,9 @@ com.sap.sailing.gwt.ui com.sap.sailing.domain.test com.sap.sse.replication.testsupport - com.sap.sse.gwt.test - --> + com.sap.sse.datamining.test com.sap.sse.security.replication.test com.sap.sse.security.test From b96dff84e4ea8aedd2838e4115a9e3f0577e7017 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Thu, 19 Feb 2015 15:27:06 +0100 Subject: [PATCH 157/264] Reworked the polar data miner now there are 3 final processors - cubic regressions for each legtype/tack combination - cubic regressions for angle clusters - moving average for each legtype/tack combination (for analysis purposes) --- .../polars/test/PolarSheetAnalyzerTest.java | 185 ----------------- .../META-INF/MANIFEST.MF | 1 - .../sap/sailing/polars/PolarDataService.java | 4 +- .../polars/analysis/PolarSheetAnalyzer.java | 13 -- .../analysis/impl/PolarSheetAnalyzerImpl.java | 134 ------------ .../mining/AngleAndSpeedRegression.java | 40 ++++ .../mining/AngleClusterPolarClusterKey.java | 12 ++ .../polars/mining/AverageAngleContainer.java | 72 +------ .../polars/mining/BasePolarClusterKey.java | 11 + .../polars/mining/BearingClusterGroup.java | 55 +++++ .../CubicRegressionPerCourseProcessor.java | 124 ++++++++++++ .../polars/mining/CubicSpeedRegressions.java | 59 ------ .../mining/GPSFixMovingWithPolarContext.java | 44 +++- ...java => MovingAveragePolarClusterKey.java} | 13 +- .../polars/mining/MovingAverageProcessor.java | 19 ++ ...r.java => MovingAverageProcessorImpl.java} | 191 ++++++++---------- .../PolarDataDimensionCollectionFactory.java | 61 +++++- .../sailing/polars/mining/PolarDataMiner.java | 98 ++++++--- .../polars/mining/PolarFixFilterCriteria.java | 27 ++- ...eedRegressionPerAngleClusterProcessor.java | 124 ++++++++++++ .../mining/TackAndLegTypePolarClusterKey.java | 15 ++ ...a => MovingAverageBoatSpeedEstimator.java} | 11 +- .../NotEnoughDataHasBeenAddedException.java | 5 + .../IncrementalAnyOrderLeastSquaresImpl.java | 51 ++++- 24 files changed, 727 insertions(+), 642 deletions(-) delete mode 100755 java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java delete mode 100755 java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java delete mode 100755 java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BasePolarClusterKey.java create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java delete mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java rename java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/{PolarClusterKey.java => MovingAveragePolarClusterKey.java} (50%) create mode 100755 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessor.java rename java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/{IncrementalRegressionProcessor.java => MovingAverageProcessorImpl.java} (57%) create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java create mode 100644 java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/TackAndLegTypePolarClusterKey.java rename java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/{BoatSpeedEstimator.java => MovingAverageBoatSpeedEstimator.java} (82%) diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java deleted file mode 100755 index 86959748977..00000000000 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.sap.sailing.polars.test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.closeTo; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.junit.Test; -import org.mockito.ArgumentMatcher; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; -import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; -import com.sap.sailing.domain.common.Bearing; -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.common.impl.KnotSpeedImpl; -import com.sap.sailing.polars.PolarDataService; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; -import com.sap.sailing.polars.analysis.impl.PolarSheetAnalyzerImpl; -import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; - -public class PolarSheetAnalyzerTest { - - @Test - public void testSpeedAndBearingCalculation() throws NotEnoughDataHasBeenAddedException { - - PolarSheetAnalyzer analyzer = new PolarSheetAnalyzerImpl(createMockedPolarDataService()); - BoatClass boatClass = mock(BoatClass.class); - - SpeedWithBearingWithConfidence result = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.UPWIND, Tack.STARBOARD, false); - assertThat(result.getObject().getKnots(), closeTo(8.5, 0.1)); - assertThat(result.getObject().getBearing().getDegrees(), closeTo(50, 0.1)); - assertThat(result.getConfidence(), closeTo(0.7, 0.1)); - - SpeedWithBearingWithConfidence result2 = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.STARBOARD, false); - assertThat(result2.getObject().getKnots(), closeTo(14.2, 0.1)); - assertThat(result2.getObject().getBearing().getDegrees(), closeTo(142.8, 0.1)); - assertThat(result2.getConfidence(), closeTo(0.6, 0.1)); - - SpeedWithBearingWithConfidence result3 = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.UPWIND, Tack.PORT, false); - assertThat(result3.getObject().getKnots(), closeTo(8.6, 0.1)); - assertThat(result3.getObject().getBearing().getDegrees(), closeTo(-49.5, 0.1)); - assertThat(result3.getConfidence(), closeTo(0.7, 0.1)); - - SpeedWithBearingWithConfidence result4 = analyzer.getAverageSpeedAndCourseOverGround(boatClass, - new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.PORT, false); - assertThat(result4.getObject().getKnots(), closeTo(13.5, 0.1)); - assertThat(result4.getObject().getBearing().getDegrees(), closeTo(-143.6, 0.1)); - assertThat(result4.getConfidence(), closeTo(0.25, 0.1)); - - } - - private PolarDataService createMockedPolarDataService() throws NotEnoughDataHasBeenAddedException { - PolarDataService mockedPolarDataService = mock(PolarDataService.class); - int[] data = createCounts(); - when(mockedPolarDataService.getDataCountsForWindSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - any(Integer.class), any(Integer.class))).thenReturn(data); - - //Starboard Upwind - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(49)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.478048671702888), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(50)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.466303997538812), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(51)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.583383077026435), 0.5, null)); - - //Starboard Downwind - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(141)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.794354053931528), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(142)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.665782579628802), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(143)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(14.169301888320263), 0.5, null)); - - //Port Upwind - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-48)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.445599410456111), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-50)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.553274292235153), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-49)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(8.614582090896583), 0.5, null)); - - //Port Downwind - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-145)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.78894715705271), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-144)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.420656294986587), 0.5, null)); - when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)), - argThat(new BearingMatcher(-143)))).thenReturn( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(13.268607457651942), 0.5, null)); - - return mockedPolarDataService; - } - - /** - * The following data is taken from a real race. (49ER yellow R2, KW 2014) - * Only the data for windspeed 14kn is used. The first block contains the - * boatspeeds for every angle to the wind. The second block contains the - * datacount for each angle (number of underlying fixes). - * - * @return - */ - private int[] createCounts() { - int[] dataCountsPerAngle = { 0, 0, 2, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 1, 2, 1, 6, 2, 2, 2, 1, 6, 6, 17, - 13, 19, 30, 43, 32, 50, 48, 57, 70, 76, 107, 129, 177, 186, 222, 237, 275, 285, 321, 322, 401, 451, - 466, 413, 468, 479, 459, 429, 360, 345, 317, 288, 278, 202, 175, 130, 113, 104, 100, 73, 45, 51, 52, - 37, 40, 36, 26, 23, 32, 33, 29, 29, 22, 24, 27, 23, 22, 23, 22, 21, 15, 18, 20, 14, 15, 21, 20, 19, 6, - 12, 10, 9, 10, 8, 1, 9, 6, 6, 14, 16, 7, 7, 6, 2, 5, 3, 6, 5, 8, 7, 14, 14, 12, 9, 20, 14, 22, 28, 14, - 20, 22, 29, 30, 38, 45, 50, 72, 87, 117, 114, 110, 145, 148, 156, 165, 150, 194, 224, 211, 196, 199, - 198, 182, 155, 125, 85, 69, 65, 49, 46, 61, 47, 48, 51, 45, 21, 14, 8, 9, 8, 8, 3, 7, 5, 2, 10, 8, 7, - 7, 10, 8, 13, 13, 11, 5, 9, 9, 8, 15, 6, 3, 5, 11, 9, 12, 5, 13, 12, 18, 15, 31, 25, 35, 36, 39, 59, - 71, 55, 52, 64, 72, 66, 70, 89, 104, 81, 120, 130, 117, 107, 113, 137, 136, 126, 108, 103, 92, 85, 76, - 65, 75, 75, 70, 57, 49, 49, 35, 44, 35, 18, 22, 17, 18, 10, 6, 4, 10, 9, 10, 8, 4, 4, 4, 10, 4, 4, 4, - 9, 8, 8, 7, 7, 6, 4, 7, 4, 1, 6, 6, 4, 6, 5, 5, 5, 6, 5, 9, 7, 12, 16, 10, 25, 25, 30, 29, 47, 44, 31, - 69, 68, 79, 62, 66, 86, 72, 84, 89, 75, 73, 62, 104, 110, 147, 137, 170, 204, 310, 347, 354, 377, 433, - 457, 419, 436, 467, 502, 513, 525, 463, 422, 405, 363, 309, 282, 262, 186, 162, 123, 97, 81, 45, 35, - 30, 24, 20, 12, 9, 8, 5, 6, 4, 6, 7, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1 }; - return dataCountsPerAngle; - } - - private class SpeedMatcher extends ArgumentMatcher { - - private final double speedToMatchInKnots; - - public SpeedMatcher(double speedToMatchInKnots) { - this.speedToMatchInKnots = speedToMatchInKnots; - } - - - @Override - public boolean matches(Object argument) { - boolean result = false; - if (argument != null) { - Speed speed = (Speed) argument; - if (speed.getKnots() > speedToMatchInKnots - 0.05 && speed.getKnots() < speedToMatchInKnots + 0.05) { - result = true; - } - } - return result; - } - - } - - private class BearingMatcher extends ArgumentMatcher { - - private final double bearingToMatchInDegrees; - - public BearingMatcher(double bearingToMatchInDegrees) { - this.bearingToMatchInDegrees = bearingToMatchInDegrees; - } - - @Override - public boolean matches(Object argument) { - boolean result = false; - if (argument != null) { - Bearing bearing = (Bearing) argument; - if (bearing.getDegrees() > bearingToMatchInDegrees - 0.4999999 - && bearing.getDegrees() < bearingToMatchInDegrees + 0.49999999) { - result = true; - } - } - return result; - } - - } - -} diff --git a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF index 77466719b0d..74883dc9799 100644 --- a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF @@ -8,7 +8,6 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ClassPath: . Export-Package: com.sap.sailing.polars, - com.sap.sailing.polars.analysis, com.sap.sailing.polars.data, com.sap.sailing.polars.factory, com.sap.sailing.polars.regression, diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java index 4a26334a7fc..3b6bfd1596c 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java @@ -17,7 +17,7 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; +import com.sap.sailing.polars.mining.MovingAverageProcessor; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; import com.sap.sse.common.Util.Pair; @@ -26,7 +26,7 @@ import com.sap.sse.common.Util.Pair; * (sometimes also referred to as a "VPP" (velocity prediction program)) makes a prediction how fast the boat will * sail at a given true wind angle and a given true wind speed.

      * - * This service uses a {@link PolarSheetAnalyzer} for more advanced analysis. Its methods are facaded in this interface for + * This service uses a {@link MovingAverageProcessor} for more advanced analysis. Its methods are facaded in this interface for * central access.

      * The interesting methods for a client are {@link #getSpeed(BoatClass, Speed, Bearing, boolean)} if data for a specific angle is * needed and {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack, boolean)} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java deleted file mode 100755 index 1f6a67d2d5e..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.sap.sailing.polars.analysis; - -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.polars.regression.NotEnoughDataHasBeenAddedException; - -public interface PolarSheetAnalyzer { - SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException; -} \ No newline at end of file diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java deleted file mode 100755 index 8734222f656..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.sap.sailing.polars.analysis.impl; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; -import com.sap.sailing.domain.base.SpeedWithConfidence; -import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.polars.PolarDataService; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; -import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; - -/** - * Extracts typical measures from polar sheets. - * - * @author Frederik Petersen (D054528) - * - */ -public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer { - - private static final int MINIMUM_DATA_COUNT_FOR_ONE_ANGLE = 5; - private final PolarDataService polarDataService; - - public PolarSheetAnalyzerImpl(PolarDataService polarDataService) { - this.polarDataService = polarDataService; - } - - @Override - public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack, boolean useRegression) - throws NotEnoughDataHasBeenAddedException { - final int startAngleInclusive; - final int endAngleExclusive; - if (legType == LegType.UPWIND && tack == Tack.STARBOARD) { - startAngleInclusive = 0; - endAngleExclusive = 90; - } else if (legType == LegType.DOWNWIND && tack == Tack.STARBOARD) { - startAngleInclusive = 90; - endAngleExclusive = 180; - } else if (legType == LegType.DOWNWIND && tack == Tack.PORT) { - startAngleInclusive = 180; - endAngleExclusive = 270; - } else if (legType == LegType.UPWIND && tack == Tack.PORT) { - startAngleInclusive = 270; - endAngleExclusive = 360; - } else { - throw new IllegalArgumentException("Leg type must be " + LegType.UPWIND.name() + " or " - + LegType.DOWNWIND.name() + " but was " + legType.name()); - } - SpeedWithBearingWithConfidence speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, - startAngleInclusive, endAngleExclusive); - return speedWithBearing; - } - - private SpeedWithBearingWithConfidence estimateAnglePeakAndAverageSpeed(BoatClass boatClass, Speed windSpeed, - int startAngleInclusive, int endAngleExclusive) throws NotEnoughDataHasBeenAddedException { - int[] dataCountPerAngle = getDataCountArray(boatClass, windSpeed, startAngleInclusive, endAngleExclusive); - double estimatedPeak = estimatePeak(boatClass, windSpeed, startAngleInclusive, endAngleExclusive, dataCountPerAngle); - double convertedAngleIfOver180 = convertAngleIfNecessary(estimatedPeak); - SpeedWithConfidence averagedSpeedWithConfidence = estimateSpeed(boatClass, windSpeed, convertedAngleIfOver180); - double originConfidence = averagedSpeedWithConfidence.getConfidence(); - double overallConfidence = (originConfidence + calcDataCountConfidence(dataCountPerAngle, estimatedPeak)) / 2; - Bearing bearing = new DegreeBearingImpl(convertedAngleIfOver180); - SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(averagedSpeedWithConfidence.getObject().getKnots(), bearing); - return new SpeedWithBearingWithConfidenceImpl<>(speedWithBearing, overallConfidence, null); - } - - /** - * Uses the formula 1-​e^​((‑x)/​150) to create a confidence for the estimated peak. - * - * If the peak has 100 data points underlying, the confidence will be approx. 0.5 - * - * @param dataCountPerAngle - * @param estimatedPeak - * @return - */ - private double calcDataCountConfidence(int[] dataCountPerAngle, double estimatedPeak) { - int dataCount = dataCountPerAngle[(int) Math.round(estimatedPeak)]; - double confidence = 1 - Math.pow(Math.E, - dataCount / 150); - return confidence; - } - - private int[] getDataCountArray(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) { - int[] dataCountPerAngle = polarDataService.getDataCountsForWindSpeed(boatClass, windSpeed, startAngleInclusive, endAngleExclusive); - return dataCountPerAngle; - } - - private double convertAngleIfNecessary(double estimatedPeak) { - double convertedAngleIfOver180 = estimatedPeak; - if (estimatedPeak > 180) { - convertedAngleIfOver180 = estimatedPeak - 360; - } - return convertedAngleIfOver180; - } - - private SpeedWithConfidence estimateSpeed(BoatClass boatClass, Speed windSpeed, double estimatedPeak) throws NotEnoughDataHasBeenAddedException { - SpeedWithConfidence speed = polarDataService.getSpeed(boatClass, windSpeed, new DegreeBearingImpl( - estimatedPeak)); - return speed; - } - - private double estimatePeak(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive, int[] dataCountPerAngle) - throws NotEnoughDataHasBeenAddedException { - // Find peak by averaging the angles that have at least 50% of the max datacount - int maxDataCount = Integer.MIN_VALUE; - for (int i = startAngleInclusive; i < endAngleExclusive; i++) { - if (dataCountPerAngle[i] > maxDataCount) { - maxDataCount = dataCountPerAngle[i]; - } - } - if (maxDataCount < MINIMUM_DATA_COUNT_FOR_ONE_ANGLE) { - // The angle with the most data points doesn't have sufficient data, for the polar data to have any - // significance. - throw new NotEnoughDataHasBeenAddedException("Only " + maxDataCount - + " points have been added for the angle with the most points. No significance."); - } - int sumOfAllUpperHalfAngles = 0; - int numberOfAnglesAdded = 0; - for (int i = startAngleInclusive; i < endAngleExclusive; i++) { - int dataCount = dataCountPerAngle[i]; - if (dataCount > maxDataCount / 2) { - sumOfAllUpperHalfAngles = sumOfAllUpperHalfAngles + i * dataCount; - numberOfAnglesAdded = numberOfAnglesAdded + dataCount; - } - } - double estimatedPeak = (double) sumOfAllUpperHalfAngles / (double) numberOfAnglesAdded; - return estimatedPeak; - } -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java new file mode 100644 index 00000000000..ac91f0dc6b4 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java @@ -0,0 +1,40 @@ +package com.sap.sailing.polars.mining; + +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.confidence.BearingWithConfidence; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.polars.regression.IncrementalLeastSquares; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; + +public class AngleAndSpeedRegression { + + private final IncrementalLeastSquares speedRegression = new IncrementalAnyOrderLeastSquaresImpl(3, false); + private final IncrementalLeastSquares angleRegression = new IncrementalAnyOrderLeastSquaresImpl(3); + + public void addData(WindWithConfidence> windSpeed, + BearingWithConfidence angleToTheWind, SpeedWithBearingWithConfidence boatSpeed) { + double windSpeedInKnots = windSpeed.getObject().getKnots(); + speedRegression.addData(windSpeedInKnots, boatSpeed.getObject().getKnots()); + angleRegression.addData(windSpeedInKnots, angleToTheWind.getObject().getDegrees()); + } + + public SpeedWithBearingWithConfidence estimateSpeedAndAngle(Speed windSpeed) throws NotEnoughDataHasBeenAddedException { + double windSpeedInKnots = windSpeed.getKnots(); + double estimatedSpeed = speedRegression.getOrCreatePolynomialFunction().value(windSpeedInKnots); + double estimatedAngle = angleRegression.getOrCreatePolynomialFunction().value(windSpeedInKnots); + Bearing bearing = new DegreeBearingImpl(estimatedAngle); + SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(estimatedSpeed, bearing); + return new SpeedWithBearingWithConfidenceImpl(speedWithBearing, /*FIXME*/ 0.5, null); + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java new file mode 100644 index 00000000000..653db2b0845 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java @@ -0,0 +1,12 @@ +package com.sap.sailing.polars.mining; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.shared.annotations.Dimension; + +public interface AngleClusterPolarClusterKey extends BasePolarClusterKey { + + @Dimension(messageKey = "angleDiffTrueWindToBoat") + Cluster getAngleDiffTrueWindToBoat(); + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AverageAngleContainer.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AverageAngleContainer.java index 305fc51a59b..eef7022d7d5 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AverageAngleContainer.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AverageAngleContainer.java @@ -1,73 +1,19 @@ package com.sap.sailing.polars.mining; -import java.util.HashMap; -import java.util.Map; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.common.Speed; -import com.sap.sse.datamining.data.Cluster; -import com.sap.sse.datamining.data.ClusterGroup; public class AverageAngleContainer { + private int dataCount = 0; + private double angleSumDeg = 0.0; + private double averageAngleDeg = 0.0; - private ClusterGroup speedClusterGroup; - - private static class DataCountAngleSumAndAverage { - private int dataCount; - private double angleSumDeg; - private double averageAngleDeg; - protected DataCountAngleSumAndAverage() { - super(); - this.dataCount = 0; - this.angleSumDeg = 0.0; - this.averageAngleDeg = 0.0; - } - public double getAverageAngleDeg() { - return averageAngleDeg; - } - public synchronized void add(double angleRad) { - dataCount++; - angleSumDeg += angleRad; - averageAngleDeg = angleSumDeg / (double) dataCount; - } - } - - private Map, DataCountAngleSumAndAverage>> dataCountAndAngleSumMap; - - public AverageAngleContainer(ClusterGroup speedClusterGroup) { - this.speedClusterGroup = speedClusterGroup; - dataCountAndAngleSumMap = new HashMap<>(); + public void addFix(double roundedAngleDeg) { + dataCount++; + angleSumDeg += roundedAngleDeg; + averageAngleDeg = angleSumDeg / (double) dataCount; } - public void addFix(BoatClass boatClass, Speed windSpeed, int roundedAngleDeg) { - Cluster windCluster = speedClusterGroup.getClusterFor(windSpeed); - if (!dataCountAndAngleSumMap.containsKey(boatClass)) { - dataCountAndAngleSumMap.put(boatClass, new HashMap<>()); - } - Map, DataCountAngleSumAndAverage> boatClassMap = dataCountAndAngleSumMap.get(boatClass); - DataCountAngleSumAndAverage triple = boatClassMap.get(windCluster); - if (triple == null) { - triple = new DataCountAngleSumAndAverage(); - boatClassMap.put(windCluster, triple); - } - triple.add(roundedAngleDeg); - } - - /** - * @return null if no entry exists for the windSpeed requested; a valid angle in degrees - * otherwise, representing the average angle of this cluster - */ - Double getAverageAngleDeg(BoatClass boatClass, Speed windSpeed) { - Double result = null; - if (dataCountAndAngleSumMap.containsKey(boatClass)) { - Map, DataCountAngleSumAndAverage> boatClassMap = dataCountAndAngleSumMap.get(boatClass); - Cluster windCluster = speedClusterGroup.getClusterFor(windSpeed); - if (boatClassMap.containsKey(windCluster)) { - DataCountAngleSumAndAverage triple = boatClassMap.get(windCluster); - result = triple.getAverageAngleDeg(); - } - } - return result; + Double getAverageAngleDeg() { + return averageAngleDeg; } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BasePolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BasePolarClusterKey.java new file mode 100644 index 00000000000..86f1fc2432b --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BasePolarClusterKey.java @@ -0,0 +1,11 @@ +package com.sap.sailing.polars.mining; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sse.datamining.shared.annotations.Dimension; + +public interface BasePolarClusterKey { + + @Dimension(messageKey = "boatClass") + BoatClass getBoatClass(); + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java new file mode 100644 index 00000000000..42f93938aad --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java @@ -0,0 +1,55 @@ +package com.sap.sailing.polars.mining; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.data.ClusterBoundary; +import com.sap.sse.datamining.impl.data.ClusterWithLowerAndUpperBoundaries; +import com.sap.sse.datamining.impl.data.ComparatorClusterBoundary; +import com.sap.sse.datamining.impl.data.ComparisonStrategy; +import com.sap.sse.datamining.impl.data.FixClusterGroup; +import com.sap.sse.i18n.ResourceBundleStringMessages; + +public class BearingClusterGroup extends FixClusterGroup{ + + public BearingClusterGroup(int startAngle, int endAngle, int clusterSize) { + super("", createClusters(startAngle, endAngle, clusterSize)); + } + + private static Collection> createClusters(int startAngle, int endAngle, int clusterSize) { + int numberOfClusters = (endAngle - startAngle) / clusterSize; + double sizeOfOneCluster = clusterSize / numberOfClusters; + List> clusters = new ArrayList>(); + for (int i = 0; i < numberOfClusters; i++) { + clusters.add(new ClusterWithLowerAndUpperBoundaries("", createBoundary(startAngle + i + * sizeOfOneCluster, ComparisonStrategy.GREATER_THAN), createBoundary(startAngle + (i + 1) + * sizeOfOneCluster, ComparisonStrategy.LOWER_EQUALS_THAN))); + } + return clusters; + } + + private static ClusterBoundary createBoundary(double angleInDeg, ComparisonStrategy strategy) { + Bearing angle = new DegreeBearingImpl(angleInDeg); + Comparator comparator = new Comparator() { + + @Override + public int compare(Bearing arg0, Bearing arg1) { + return new Double(arg0.getDegrees()).compareTo(new Double(arg1.getDegrees())); + } + }; + return new ComparatorClusterBoundary(comparator, angle, strategy); + } + + @Override + public String getLocalizedName(Locale locale, ResourceBundleStringMessages stringMessages) { + return ""; + } + + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java new file mode 100644 index 00000000000..39b4e94a5b2 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java @@ -0,0 +1,124 @@ +package com.sap.sailing.polars.mining; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +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.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sse.datamining.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; + +public class CubicRegressionPerCourseProcessor implements Processor, Void> { + + private static final Logger logger = Logger.getLogger(CubicRegressionPerCourseProcessor.class.getName()); + + private final Map regressions = new HashMap<>(); + + @Override + public void processElement(GroupedDataEntry element) { + GroupKey key = element.getKey(); + AngleAndSpeedRegression regression; + synchronized (regressions) { + regression = regressions.get(key); + if (regression == null) { + regression = new AngleAndSpeedRegression(); + regressions.put(key, regression); + } + } + GPSFixMovingWithPolarContext fix = element.getDataEntry(); + regression.addData(fix.getWindSpeed(), fix.getAngleToTheWind(), fix.getBoatSpeed()); + } + + public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { + GroupKey key = createGroupKey(boatClass, legType, tack); + SpeedWithBearingWithConfidence 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; + } + + public Set> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass, + Speed speedOverGround, LegType legType, Tack tack) { + // TODO Auto-generated method stub + return null; + } + + private GroupKey createGroupKey(final BoatClass boatClass, final LegType legType, + final Tack tack) { + TackAndLegTypePolarClusterKey key = new TackAndLegTypePolarClusterKey() { + + @Override + public BoatClass getBoatClass() { + return boatClass; + } + + @Override + public Tack getTack() { + return tack; + } + + @Override + public LegType getLegType() { + return legType; + } + }; + GroupKey compoundKey; + try { + compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory + .getCubicRegressionPerCourseClusterKeyDimensions().iterator()); + } 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); + } + + + @Override + public Class> getInputType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Class 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 void abort() { + // TODO Auto-generated method stub + } + + @Override + public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java deleted file mode 100644 index 86f865723db..00000000000 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicSpeedRegressions.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.sap.sailing.polars.mining; - -import java.util.HashMap; -import java.util.Map; - -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.polars.regression.IncrementalLeastSquares; -import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; -import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; -import com.sap.sse.common.Util.Pair; - -public class CubicSpeedRegressions { - - private final Map, IncrementalLeastSquares> regressions; - - public CubicSpeedRegressions() { - regressions = new HashMap<>(); - for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { - for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { - regressions.put(new Pair(legType, tack), - new IncrementalAnyOrderLeastSquaresImpl(4, false)); - } - } - } - - public void addFix(GPSFixMovingWithPolarContext fix) { - final LegType legType; - final Tack tack; - final int roundedAngleDeg = fix.getRoundedTrueWindAngle().getAngleDeg(); - if (roundedAngleDeg < -90) { - legType = LegType.DOWNWIND; - tack = Tack.PORT; - } else if (roundedAngleDeg < 0) { - legType = LegType.UPWIND; - tack = Tack.PORT; - } else if (roundedAngleDeg > 90) { - legType = LegType.DOWNWIND; - tack = Tack.STARBOARD; - } else { // roundedAngleDeg > 0 && roundedAngleDeg <= 90 - legType = LegType.UPWIND; - tack = Tack.STARBOARD; - } - IncrementalLeastSquares leastSquares = regressions.get(new Pair(legType, tack)); - leastSquares.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); - } - - public double estimateSpeedInKnots(LegType legType, Tack tack, double windSpeedInKnots) { - double result; - try { - result = regressions.get(new Pair(legType, tack)).getOrCreatePolynomialFunction().value(windSpeedInKnots); - } catch (NotEnoughDataHasBeenAddedException e) { - //FIXME try to come up with something smarter - result = 0; - } - return result; - } - -} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java index a6f89a5e5ec..694f4b4fbb7 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java @@ -7,9 +7,11 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.confidence.BearingWithConfidence; @@ -24,7 +26,7 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.data.Cluster; import com.sap.sse.datamining.data.ClusterGroup; -public class GPSFixMovingWithPolarContext implements PolarClusterKey { +public class GPSFixMovingWithPolarContext implements MovingAveragePolarClusterKey, AngleClusterPolarClusterKey { private final GPSFixMoving fix; private final TrackedRace race; @@ -32,13 +34,15 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey { private final ClusterGroup windSpeedClusterGroup; private final Set windSourcesToExcludeForBearing; private final Set windSourcesToExcludeForSpeed; + private final ClusterGroup angleClusterGroup; public GPSFixMovingWithPolarContext(GPSFixMoving fix, TrackedRace race, Competitor competitor, - ClusterGroup windSpeedClusterGroup) { + ClusterGroup windSpeedClusterGroup, ClusterGroup angleClusterGroup) { this.fix = fix; this.race = race; this.competitor = competitor; this.windSpeedClusterGroup = windSpeedClusterGroup; + this.angleClusterGroup = angleClusterGroup; this.windSourcesToExcludeForBearing = collectWindSourcesToIgnoreForBearing(); this.windSourcesToExcludeForSpeed = collectWindSourcesToIgnoreForSpeed(); } @@ -55,12 +59,6 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey { return competitor; } - @Override - public RoundedTrueWindAngle getRoundedTrueWindAngle() { - final BearingWithConfidence angleToTheWind = getAngleToTheWind(); - return angleToTheWind == null ? null : new RoundedTrueWindAngle(angleToTheWind.getObject()); - } - public BearingWithConfidence getAngleToTheWind() { SpeedWithBearing boatSpeed = race.getTrack(competitor).getEstimatedSpeed(fix.getTimePoint()); WindWithConfidence> wind = race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint(), windSourcesToExcludeForBearing); @@ -148,4 +146,34 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey { return race.getRace().getBoatClass(); } + @Override + public Tack getTack() { + return getTack(getAngleToTheWind().getObject()); + } + + public static Tack getTack(Bearing angleDifferenceWindToBoat) { + return angleDifferenceWindToBoat.getDegrees() <= 0 ? Tack.PORT : Tack.STARBOARD; + } + + @Override + public LegType getLegType() { + return getLegTypeWithPolarConstraints(getAngleToTheWind().getObject()); + } + + public static LegType getLegTypeWithPolarConstraints(Bearing angleDifferenceWindToBoat) { + double absoluteDiffInDeg = Math.abs(angleDifferenceWindToBoat.getDegrees()); + LegType result = null; + if (absoluteDiffInDeg > 20 && absoluteDiffInDeg < 75) { + result = LegType.UPWIND; + } else if (absoluteDiffInDeg > 105) { + result = LegType.DOWNWIND; + } + return result; + } + + @Override + public Cluster getAngleDiffTrueWindToBoat() { + return angleClusterGroup.getClusterFor(getAngleToTheWind().getObject()); + } + } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAveragePolarClusterKey.java similarity index 50% rename from java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarClusterKey.java rename to java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAveragePolarClusterKey.java index 83b443ded5a..c54228800de 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarClusterKey.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAveragePolarClusterKey.java @@ -1,18 +1,10 @@ package com.sap.sailing.polars.mining; -import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.common.Speed; import com.sap.sse.datamining.data.Cluster; import com.sap.sse.datamining.shared.annotations.Dimension; -public interface PolarClusterKey { - - /** - * - * @return degrees boat to windDirection in DEG - */ - @Dimension(messageKey = "angleToWind") - RoundedTrueWindAngle getRoundedTrueWindAngle(); +public interface MovingAveragePolarClusterKey extends TackAndLegTypePolarClusterKey { /** * @@ -21,7 +13,4 @@ public interface PolarClusterKey { @Dimension(messageKey = "windSpeed") Cluster getWindSpeedCluster(); - @Dimension(messageKey = "boatClass") - BoatClass getBoatClass(); - } \ No newline at end of file diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessor.java new file mode 100755 index 00000000000..c4f704620fc --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessor.java @@ -0,0 +1,19 @@ +package com.sap.sailing.polars.mining; + +import java.util.Set; + +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.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.GroupedDataEntry; + +public interface MovingAverageProcessor extends Processor, Void>{ + SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, + Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; + + Set getAvailableBoatClasses(); +} \ No newline at end of file diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java similarity index 57% rename from java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java rename to java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java index 436e41d8baa..e9cfaaae93d 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java @@ -24,103 +24,67 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.tracking.WindWithConfidence; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; -import com.sap.sailing.polars.regression.BoatSpeedEstimator; +import com.sap.sailing.polars.regression.MovingAverageBoatSpeedEstimator; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; import com.sap.sailing.polars.regression.impl.WindSpeedAndAngleEstimator; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.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; -public class IncrementalRegressionProcessor implements Processor, Void>, - PolarSheetAnalyzer { +public class MovingAverageProcessorImpl implements MovingAverageProcessor { - private static final Logger logger = Logger.getLogger(IncrementalRegressionProcessor.class.getName()); + private static final Logger logger = Logger.getLogger(MovingAverageProcessorImpl.class.getName()); - private final Map boatSpeedEstimators = new HashMap(); + private final Map boatSpeedEstimators = new HashMap<>(); + + private final Map averageAngleContainers = new HashMap<>(); private final ClusterGroup speedClusterGroup; private final Set availableBoatClasses = new HashSet<>(); - private final Map, AverageAngleContainer> averageAngleContainers; - - private final Map cubicRegressions = new HashMap<>(); - public IncrementalRegressionProcessor(ClusterGroup speedClusterGroup) { + + public MovingAverageProcessorImpl(ClusterGroup speedClusterGroup) { this.speedClusterGroup = speedClusterGroup; - averageAngleContainers = new HashMap<>(); - for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { - for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { - averageAngleContainers.put(new Pair(legType, tack), - new AverageAngleContainer(speedClusterGroup)); - } - } } @Override public void processElement(GroupedDataEntry element) { GroupKey key = element.getKey(); - BoatSpeedEstimator boatSpeedEstimator; + MovingAverageBoatSpeedEstimator boatSpeedEstimator; synchronized (boatSpeedEstimators) { boatSpeedEstimator = boatSpeedEstimators.get(key); if (boatSpeedEstimator == null) { - boatSpeedEstimator = new BoatSpeedEstimator(); + boatSpeedEstimator = new MovingAverageBoatSpeedEstimator(); boatSpeedEstimators.put(key, boatSpeedEstimator); - availableBoatClasses.add(element.getDataEntry().getBoatClass()); + } + } + AverageAngleContainer averageAngleContainer; + synchronized (averageAngleContainers) { + averageAngleContainer = averageAngleContainers.get(key); + if (averageAngleContainer == null) { + averageAngleContainer = new AverageAngleContainer(); + averageAngleContainers.put(key, averageAngleContainer); } } GPSFixMovingWithPolarContext fix = element.getDataEntry(); BearingWithConfidence angleToTheWind = fix.getAngleToTheWind(); WindWithConfidence> windSpeed = fix.getWindSpeed(); SpeedWithBearingWithConfidence boatSpeedWithConfidence = fix.getBoatSpeed(); - // Only add GPS data if speeds and angles are not null, else do nothing! - if (angleToTheWind != null && windSpeed != null && boatSpeedWithConfidence != null) { - fillAverageAngleContainer(fix, element, windSpeed); - fillCubicRegressions(fix); - WindWithConfidence> windWithConfidenceForSpeed = windSpeed; - double confidenceForWindSpeed = windWithConfidenceForSpeed.getConfidence(); - double confidenceForWindBearing = angleToTheWind.getConfidence(); - double confidenceForBoatSpeed = boatSpeedWithConfidence.getConfidence(); - double averagedConfidence = (confidenceForBoatSpeed + confidenceForWindBearing + confidenceForWindSpeed) / 3; - boatSpeedEstimator.addData(boatSpeedWithConfidence.getObject(), averagedConfidence); - } - } - - private void fillCubicRegressions(GPSFixMovingWithPolarContext fix) { - if (!cubicRegressions.containsKey(fix.getBoatClass())) { - cubicRegressions.put(fix.getBoatClass(), new CubicSpeedRegressions()); - } - cubicRegressions.get(fix.getBoatClass()).addFix(fix); - } - - private void fillAverageAngleContainer(GPSFixMovingWithPolarContext fix, - GroupedDataEntry element, - WindWithConfidence> windSpeed) { - final int roundedAngleDeg = fix.getRoundedTrueWindAngle().getAngleDeg(); - final LegType legType; - final Tack tack; - if (roundedAngleDeg < -90) { - legType = LegType.DOWNWIND; - tack = Tack.PORT; - } else if (roundedAngleDeg < 0) { - legType = LegType.UPWIND; - tack = Tack.PORT; - } else if (roundedAngleDeg > 90) { - legType = LegType.DOWNWIND; - tack = Tack.STARBOARD; - } else { // roundedAngleDeg > 0 && roundedAngleDeg <= 90 - legType = LegType.UPWIND; - tack = Tack.STARBOARD; - } - averageAngleContainers.get(new Pair<>(legType, tack)).addFix(element.getDataEntry().getBoatClass(), - windSpeed.getObject(), roundedAngleDeg); + availableBoatClasses.add(element.getDataEntry().getBoatClass()); + WindWithConfidence> windWithConfidenceForSpeed = windSpeed; + double confidenceForWindSpeed = windWithConfidenceForSpeed.getConfidence(); + double confidenceForWindBearing = angleToTheWind.getConfidence(); + double confidenceForBoatSpeed = boatSpeedWithConfidence.getConfidence(); + double averagedConfidence = (confidenceForBoatSpeed + confidenceForWindBearing + confidenceForWindSpeed) / 3; + averageAngleContainer.addFix(angleToTheWind.getObject().getDegrees()); + boatSpeedEstimator.addData(boatSpeedWithConfidence.getObject(), averagedConfidence); } @Override @@ -155,42 +119,25 @@ public class IncrementalRegressionProcessor implements Processor getWindSpeedCluster() { - return speedClusterGroup.getClusterFor(windSpeed); - } - }; - GroupKey compoundKey; - try { - compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory - .getClusterKeyDimensions().iterator()); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - BoatSpeedEstimator boatSpeedEstimator = boatSpeedEstimators.get(compoundKey); + private SpeedWithConfidenceAndDataCount estimateBoatSpeed(GroupKey key) throws NotEnoughDataHasBeenAddedException { + MovingAverageBoatSpeedEstimator boatSpeedEstimator = boatSpeedEstimators.get(key); if (boatSpeedEstimator == null) { throw new NotEnoughDataHasBeenAddedException(); } - Speed speedWithoutConfidence = boatSpeedEstimator.estimateSpeed(windSpeed.getKnots(), trueWindAngle.getDegrees()); + Speed speedWithoutConfidence = boatSpeedEstimator.estimateSpeed(); final int dataCount = boatSpeedEstimator.getDataCount(); double confidence = boatSpeedEstimator.getConfidence(); return new SpeedWithConfidenceAndDataCount(new SpeedWithConfidenceImpl(speedWithoutConfidence, confidence, null), dataCount); } + private Bearing estimateAngle(GroupKey key) throws NotEnoughDataHasBeenAddedException { + AverageAngleContainer averageAngleContainer = averageAngleContainers.get(key); + if (averageAngleContainer == null) { + throw new NotEnoughDataHasBeenAddedException(); + } + return new DegreeBearingImpl(averageAngleContainer.getAverageAngleDeg()); + } + Set> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass, Speed speedOverGround, LegType legType, Tack tack) { List>> averageBoatSpeedAndCourseForWindSpeed = obtainPolarSamplingPoints( @@ -208,7 +155,7 @@ public class IncrementalRegressionProcessor implements Processor averageSpeedAndCourseOverGround; try { averageSpeedAndCourseOverGround = getAverageSpeedAndCourseOverGround( - boatClass, windSpeed, legType, tack, false); + boatClass, windSpeed, legType, tack); averageBoatSpeedAndCourseForWindSpeed.add(new Pair>(windSpeed, averageSpeedAndCourseOverGround)); } catch (NotEnoughDataHasBeenAddedException e) { @@ -242,30 +189,52 @@ public class IncrementalRegressionProcessor implements Processor getAverageSpeedAndCourseOverGround(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack, boolean useCubicRegressionForSpeed) throws NotEnoughDataHasBeenAddedException { - Double averageAngle = averageAngleContainers.get(new Pair<>(legType, tack)).getAverageAngleDeg( - boatClass, windSpeed); - SpeedWithBearingWithConfidence result; - if (useCubicRegressionForSpeed) { - DegreeBearingImpl angleToTheWind = new DegreeBearingImpl(averageAngle); - SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(cubicRegressions.get(boatClass) - .estimateSpeedInKnots(legType, tack, windSpeed.getKnots()), angleToTheWind); - result = new SpeedWithBearingWithConfidenceImpl(speedWithBearing, /* FIXME */0.5, null); - } else { - result = estimateSpeedForAverageAngle(boatClass, windSpeed, averageAngle); - } + Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { + SpeedWithBearingWithConfidence result = estimateSpeedAndAngle(boatClass, windSpeed, legType, tack); return result; } - private SpeedWithBearingWithConfidence estimateSpeedForAverageAngle(BoatClass boatClass, Speed windSpeed, - Double averageAngle) throws NotEnoughDataHasBeenAddedException { - if (averageAngle == null) { - throw new NotEnoughDataHasBeenAddedException(); - } - DegreeBearingImpl angleToTheWind = new DegreeBearingImpl(averageAngle); - SpeedWithConfidence estimatedSpeed = estimateBoatSpeed(boatClass, windSpeed, angleToTheWind).getSpeedWithConfidence(); - SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(estimatedSpeed.getObject().getKnots(), angleToTheWind); + private SpeedWithBearingWithConfidence estimateSpeedAndAngle(BoatClass boatClass, Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { + GroupKey key = createGroupKey(boatClass, windSpeed, legType, tack); + Bearing angleDiffTrueWindToBoat = estimateAngle(key); + SpeedWithConfidence estimatedSpeed = estimateBoatSpeed(key).getSpeedWithConfidence(); + SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(estimatedSpeed.getObject().getKnots(), angleDiffTrueWindToBoat); return new SpeedWithBearingWithConfidenceImpl(speedWithBearing, estimatedSpeed.getConfidence(), null); } + + private GroupKey createGroupKey(final BoatClass boatClass, final Speed windSpeed, final LegType legType, + final Tack tack) { + MovingAveragePolarClusterKey key = new MovingAveragePolarClusterKey() { + + @Override + public BoatClass getBoatClass() { + return boatClass; + } + + @Override + public Cluster getWindSpeedCluster() { + return speedClusterGroup.getClusterFor(windSpeed); + } + + @Override + public Tack getTack() { + return tack; + } + + @Override + public LegType getLegType() { + return legType; + } + }; + GroupKey compoundKey; + try { + compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory + .getMovingAverageClusterKeyDimensions().iterator()); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + return compoundKey; + } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java index 14a342f1be7..656b7f985b0 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java @@ -4,26 +4,67 @@ import java.util.ArrayList; import java.util.Collection; import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.Tack; import com.sap.sse.datamining.factories.FunctionFactory; import com.sap.sse.datamining.functions.Function; public class PolarDataDimensionCollectionFactory { - public static Collection> getClusterKeyDimensions() throws NoSuchMethodException { + public static Collection> getMovingAverageClusterKeyDimensions() throws NoSuchMethodException { Collection> dimensions = new ArrayList<>(); FunctionFactory functionFactory = new FunctionFactory(); - Function angleFunction = functionFactory - .createMethodWrappingFunction(PolarClusterKey.class.getMethod("getRoundedTrueWindAngle", - new Class[0])); - Function windSpeedFunction = functionFactory.createMethodWrappingFunction(PolarClusterKey.class - .getMethod("getWindSpeedCluster", new Class[0])); - Function boatClassFunction = functionFactory.createMethodWrappingFunction(PolarClusterKey.class - .getMethod("getBoatClass", new Class[0])); + addTackAndLegTypeDimensions(dimensions, functionFactory); - dimensions.add(boatClassFunction); - dimensions.add(angleFunction); + addPolarBaseDimension(dimensions, functionFactory); + + + + Function windSpeedFunction = functionFactory.createMethodWrappingFunction(MovingAveragePolarClusterKey.class + .getMethod("getWindSpeedCluster", new Class[0])); dimensions.add(windSpeedFunction); return dimensions; } + private static void addTackAndLegTypeDimensions(Collection> dimensions, FunctionFactory functionFactory) + throws NoSuchMethodException { + Function tackFunction = functionFactory + .createMethodWrappingFunction(MovingAveragePolarClusterKey.class.getMethod("getTack", + new Class[0])); + Function legTypeFunction = functionFactory + .createMethodWrappingFunction(MovingAveragePolarClusterKey.class.getMethod("getLegType", + new Class[0])); + dimensions.add(tackFunction); + dimensions.add(legTypeFunction); + } + + private static void addPolarBaseDimension(Collection> dimensions, FunctionFactory functionFactory) + throws NoSuchMethodException { + Function boatClassFunction = functionFactory.createMethodWrappingFunction(MovingAveragePolarClusterKey.class + .getMethod("getBoatClass", new Class[0])); + dimensions.add(boatClassFunction); + } + + public static Collection> getCubicRegressionPerCourseClusterKeyDimensions() throws NoSuchMethodException { + Collection> dimensions = new ArrayList<>(); + FunctionFactory functionFactory = new FunctionFactory(); + addTackAndLegTypeDimensions(dimensions, functionFactory); + + addPolarBaseDimension(dimensions, functionFactory); + return dimensions; + } + + public static Collection> getSpeedRegressionPerAngleClusterClusterKeyDimensions() throws NoSuchMethodException { + Collection> dimensions = new ArrayList<>(); + FunctionFactory functionFactory = new FunctionFactory(); + + addPolarBaseDimension(dimensions, functionFactory); + + Function angleDiffTrueWindToBoatFunction = functionFactory.createMethodWrappingFunction(AngleClusterPolarClusterKey.class + .getMethod("getAngleDiffTrueWindToBoat", new Class[0])); + dimensions.add(angleDiffTrueWindToBoatFunction); + + return dimensions; + } + } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index 3ab5dd6958c..fd9a5504c6c 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -30,8 +30,6 @@ import com.sap.sailing.domain.common.impl.PolarSheetsHistogramDataImpl; import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.polars.analysis.PolarSheetAnalyzer; -import com.sap.sailing.polars.mining.IncrementalRegressionProcessor.SpeedWithConfidenceAndDataCount; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.data.ClusterGroup; @@ -40,7 +38,7 @@ import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.ParallelFilteringProcessor; import com.sap.sse.datamining.impl.components.ParallelMultiDimensionsValueNestingGroupingProcessor; -public class PolarDataMiner implements PolarSheetAnalyzer { +public class PolarDataMiner { private static final int THREAD_POOL_SIZE = Math.min(Runtime.getRuntime().availableProcessors(), 3); private static final ThreadPoolExecutor executor = createExecutor(); @@ -53,7 +51,19 @@ public class PolarDataMiner implements PolarSheetAnalyzer { private AbstractEnrichingProcessor enrichingProcessor; - private IncrementalRegressionProcessor incrementalRegressionProcessor; + + /** + * This processor keeps track of the moving average of the speed values and the average angle for each course (legtype tack combination) + */ + private MovingAverageProcessor movingAverageProcessor; + + /** + * This processor uses two cubic regressions angle to the true wind over windspeed and boatspeed over windspeed for each + * course (legtype tack combination) + */ + private CubicRegressionPerCourseProcessor cubicRegressionPerCourseProcessor; + + private SpeedRegressionPerAngleClusterProcessor speedRegressionPerAngleClusterProcessor; public PolarDataMiner() { this(PolarSheetGenerationSettingsImpl.createBackendPolarSettings()); @@ -74,16 +84,41 @@ public class PolarDataMiner implements PolarSheetAnalyzer { WindSpeedSteppingWithMaxDistance stepping = backendPolarSheetGenerationSettings.getWindSpeedStepping(); final ClusterGroup speedClusterGroup = SpeedClusterGroupFromWindSteppingCreator .createSpeedClusterGroupFrom(stepping); - incrementalRegressionProcessor = new IncrementalRegressionProcessor(speedClusterGroup); - Collection, ?>> grouperResultReceivers = new ArrayList, ?>>(); - grouperResultReceivers.add(incrementalRegressionProcessor); + movingAverageProcessor = new MovingAverageProcessorImpl(speedClusterGroup); + Collection, ?>> movingAverageGrouperResultReceivers = new ArrayList, ?>>(); + movingAverageGrouperResultReceivers.add(movingAverageProcessor); - Collection> dimensions = PolarDataDimensionCollectionFactory.getClusterKeyDimensions(); + Collection> movingAverageDimensions = PolarDataDimensionCollectionFactory.getMovingAverageClusterKeyDimensions(); - Processor> groupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( - GPSFixMovingWithPolarContext.class, executor, grouperResultReceivers, dimensions); + Processor> movingAverageGroupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( + GPSFixMovingWithPolarContext.class, executor, movingAverageGrouperResultReceivers, movingAverageDimensions); + - Collection> filteringResultReceivers = Arrays.asList(groupingProcessor); + cubicRegressionPerCourseProcessor = new CubicRegressionPerCourseProcessor(); + Collection, ?>> regressionPerCourseGrouperResultReceivers = new ArrayList, ?>>(); + regressionPerCourseGrouperResultReceivers.add(cubicRegressionPerCourseProcessor); + + Collection> cubicRegressionPerCourseDimensions = PolarDataDimensionCollectionFactory.getCubicRegressionPerCourseClusterKeyDimensions(); + + Processor> cubicRegressionPerCourseGroupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( + GPSFixMovingWithPolarContext.class, executor, regressionPerCourseGrouperResultReceivers, cubicRegressionPerCourseDimensions); + + ClusterGroup angleClusterGroup = createAngleClusterGroup(); + speedRegressionPerAngleClusterProcessor = new SpeedRegressionPerAngleClusterProcessor(angleClusterGroup); + Collection, ?>> regressionPerAngleClusterGrouperResultReceivers = new ArrayList, ?>>(); + regressionPerAngleClusterGrouperResultReceivers.add(speedRegressionPerAngleClusterProcessor); + + Collection> regressionPerAngleClusterDimensions = PolarDataDimensionCollectionFactory.getSpeedRegressionPerAngleClusterClusterKeyDimensions(); + + Processor> regressionPerAngleClusterGroupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( + GPSFixMovingWithPolarContext.class, executor, regressionPerAngleClusterGrouperResultReceivers, regressionPerAngleClusterDimensions); + + + Collection> filteringResultReceivers = new ArrayList<>(); + filteringResultReceivers.add(movingAverageGroupingProcessor); + filteringResultReceivers.add(cubicRegressionPerCourseGroupingProcessor); + filteringResultReceivers.add(regressionPerAngleClusterGroupingProcessor); + Processor filteringProcessor = new ParallelFilteringProcessor( GPSFixMovingWithPolarContext.class, executor, filteringResultReceivers, new PolarFixFilterCriteria(backendPolarSheetGenerationSettings.getNumberOfLeadingCompetitorsToInclude())); @@ -97,12 +132,16 @@ public class PolarDataMiner implements PolarSheetAnalyzer { @Override protected GPSFixMovingWithPolarContext enrich(GPSFixMovingWithOriginInfo element) { return new GPSFixMovingWithPolarContext(element.getFix(), element.getTrackedRace(), element.getCompetitor(), - speedClusterGroup); + speedClusterGroup, angleClusterGroup); } }; } + private ClusterGroup createAngleClusterGroup() { + return new BearingClusterGroup(-180, 180, 5); + } + public void addFix(GPSFixMoving fix, Competitor competitor, TrackedRace trackedRace) { enrichingProcessor.processElement(new GPSFixMovingWithOriginInfo(fix, trackedRace, competitor)); @@ -124,12 +163,12 @@ public class PolarDataMiner implements PolarSheetAnalyzer { */ public SpeedWithConfidence estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle) throws NotEnoughDataHasBeenAddedException { - return incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed, trueWindAngle).getSpeedWithConfidence(); + return speedRegressionPerAngleClusterProcessor.estimateBoatSpeed(boatClass, windSpeed, trueWindAngle); } public Set> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass, Speed speedOverGround, LegType legType, Tack tack) { - return incrementalRegressionProcessor.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); + return cubicRegressionPerCourseProcessor.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); } public PolarSheetsData createFullSheetForBoatClass(BoatClass boatClass) { @@ -146,19 +185,18 @@ public class PolarDataMiner implements PolarSheetAnalyzer { Integer[] perAngle = new Integer[360]; Map perWindSpeed = new HashMap<>(); for (int angle = 0; angle < 360; angle++) { - SpeedWithConfidenceAndDataCount speedWithConfidenceAndDataCount; + SpeedWithConfidence speedWithConfidence; try { int convertedAngle = convertAngleIfNecessary(angle); - speedWithConfidenceAndDataCount = incrementalRegressionProcessor.estimateBoatSpeed(boatClass, + speedWithConfidence = speedRegressionPerAngleClusterProcessor.estimateBoatSpeed(boatClass, new KnotSpeedImpl(windSpeed), new DegreeBearingImpl(convertedAngle)); } catch (NotEnoughDataHasBeenAddedException e) { // No data so put in a 0 speed with 0 confidence - speedWithConfidenceAndDataCount = new SpeedWithConfidenceAndDataCount( - new SpeedWithConfidenceImpl(new KnotSpeedImpl(0), 0, null), 0); + speedWithConfidence = new SpeedWithConfidenceImpl(new KnotSpeedImpl(0), 0, null); } - averagedPolarDataByWindSpeed[windIndex][angle] = speedWithConfidenceAndDataCount.getSpeedWithConfidence().getObject().getKnots(); - int dataCount = speedWithConfidenceAndDataCount.getDataCount(); + averagedPolarDataByWindSpeed[windIndex][angle] = speedWithConfidence.getObject().getKnots(); + int dataCount = 0; /*FIXME*/ totalDataCount = totalDataCount + dataCount; // FIXME hard coded @@ -200,19 +238,14 @@ public class PolarDataMiner implements PolarSheetAnalyzer { } public Set getAvailableBoatClasses() { - return incrementalRegressionProcessor.getAvailableBoatClasses(); + return movingAverageProcessor.getAvailableBoatClasses(); } public int[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) { int[] dataCounts = new int[360]; for (int angle = 0; angle < 360; angle++) { if (angle >= startAngleInclusive && angle < endAngleExclusive) { - try { - dataCounts[angle] = incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed, - new DegreeBearingImpl(convertAngleIfNecessary(angle))).getDataCount(); - } catch (NotEnoughDataHasBeenAddedException e) { - dataCounts[angle] = 0; - } + dataCounts[angle] = 0; /*FIXME*/ } else { dataCounts[angle] = -1; } @@ -220,9 +253,14 @@ public class PolarDataMiner implements PolarSheetAnalyzer { return dataCounts; } - @Override public SpeedWithBearingWithConfidence getAverageSpeedAndCourseOverGround(BoatClass boatClass, - Speed windSpeed, LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException { - return incrementalRegressionProcessor.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack, useRegressionForSpeed); + Speed windSpeed, LegType legType, Tack tack, boolean useRegressionForSpeed) throws NotEnoughDataHasBeenAddedException { + SpeedWithBearingWithConfidence averageSpeedAndCourseOverGround = null; + if (useRegressionForSpeed) { + averageSpeedAndCourseOverGround = cubicRegressionPerCourseProcessor.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack); + } else { + averageSpeedAndCourseOverGround = movingAverageProcessor.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack); + } + return averageSpeedAndCourseOverGround; } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java index 656b7ff5ca7..c91b91082a6 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java @@ -4,11 +4,16 @@ import java.util.Iterator; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.confidence.BearingWithConfidence; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.components.FilterCriterion; public class PolarFixFilterCriteria implements FilterCriterion { @@ -35,6 +40,11 @@ public class PolarFixFilterCriteria implements FilterCriterion 0) { isInLeadingCompetitors = isInLeadingCompetitors(element); } - return (afterStartTime && beforeFinishTime && noDirectionChange && isInLeadingCompetitors); + return (importantDataIsNotNull && hasLegType && afterStartTime && beforeFinishTime && noDirectionChange && isInLeadingCompetitors); } + private boolean importantDataIsNotNull(GPSFixMovingWithPolarContext element) { + BearingWithConfidence angleToTheWind = element.getAngleToTheWind(); + WindWithConfidence> windSpeed = element.getWindSpeed(); + SpeedWithBearingWithConfidence boatSpeedWithConfidence = element.getBoatSpeed(); + boolean result = false; + if (angleToTheWind != null && windSpeed != null && boatSpeedWithConfidence != null) { + result = true; + } + return result; + } + + private boolean hasLegType(GPSFixMovingWithPolarContext element) { + return element.getLegType() != null; + } + private boolean isInLeadingCompetitors(GPSFixMovingWithPolarContext element) { boolean result = false; Iterator finishPassings = element.getRace() diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java new file mode 100644 index 00000000000..722d6ffbd10 --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java @@ -0,0 +1,124 @@ +package com.sap.sailing.polars.mining; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +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.KnotSpeedImpl; +import com.sap.sailing.polars.regression.IncrementalLeastSquares; +import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; +import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; +import com.sap.sse.datamining.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; + +public class SpeedRegressionPerAngleClusterProcessor implements Processor, Void>{ + +private static final Logger logger = Logger.getLogger(CubicRegressionPerCourseProcessor.class.getName()); + + private final Map regressions = new HashMap<>(); + + private final ClusterGroup angleClusterGroup; + + public SpeedRegressionPerAngleClusterProcessor(ClusterGroup angleClusterGroup) { + this.angleClusterGroup = angleClusterGroup; + } + + @Override + public void processElement(GroupedDataEntry element) { + GroupKey key = element.getKey(); + IncrementalLeastSquares regression; + 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.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); + } + + public SpeedWithConfidence estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle) throws NotEnoughDataHasBeenAddedException { + GroupKey key = createGroupKey(boatClass, trueWindAngle); + double estimatedSpeed; + if (regressions.containsKey(key)) { + estimatedSpeed = regressions.get(key).getOrCreatePolynomialFunction().value(windSpeed.getKnots()); + } else { + throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions"); + } + Speed speed = new KnotSpeedImpl(estimatedSpeed); + return new SpeedWithConfidenceImpl(speed, /*FIXME*/ 0.5, null); + } + + private GroupKey createGroupKey(final BoatClass boatClass, final Bearing angle) { + AngleClusterPolarClusterKey key = new AngleClusterPolarClusterKey() { + + @Override + public BoatClass getBoatClass() { + return boatClass; + } + + @Override + public Cluster getAngleDiffTrueWindToBoat() { + return angleClusterGroup.getClusterFor(angle); + } + + }; + GroupKey compoundKey; + try { + compoundKey = GroupKeyFactory.createNestingCompoundKeyFor(key, PolarDataDimensionCollectionFactory + .getSpeedRegressionPerAngleClusterClusterKeyDimensions().iterator()); + } 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); + } + + + @Override + public Class> getInputType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Class 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 void abort() { + // TODO Auto-generated method stub + } + + @Override + public AdditionalResultDataBuilder getAdditionalResultData(AdditionalResultDataBuilder additionalDataBuilder) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/TackAndLegTypePolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/TackAndLegTypePolarClusterKey.java new file mode 100644 index 00000000000..c783abcc6bf --- /dev/null +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/TackAndLegTypePolarClusterKey.java @@ -0,0 +1,15 @@ +package com.sap.sailing.polars.mining; + +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.Tack; +import com.sap.sse.datamining.shared.annotations.Dimension; + +public interface TackAndLegTypePolarClusterKey extends BasePolarClusterKey { + + @Dimension(messageKey = "tack") + Tack getTack(); + + @Dimension(messageKey = "legType") + LegType getLegType(); + +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/MovingAverageBoatSpeedEstimator.java similarity index 82% rename from java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java rename to java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/MovingAverageBoatSpeedEstimator.java index dcfb956e2e8..e9dc394912e 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/BoatSpeedEstimator.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/MovingAverageBoatSpeedEstimator.java @@ -5,7 +5,7 @@ import java.io.Serializable; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; -import com.sap.sailing.polars.mining.IncrementalRegressionProcessor; +import com.sap.sailing.polars.mining.MovingAverageProcessorImpl; import com.sap.sse.concurrent.LockUtil; import com.sap.sse.concurrent.NamedReentrantReadWriteLock; @@ -17,13 +17,13 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock; *

      * * Note that this class should only be used for a small wind interval on a polar sheet. The - * {@link IncrementalRegressionProcessor} is one example for that. It has one instance of the {@link BoatSpeedEstimator} + * {@link MovingAverageProcessorImpl} is one example for that. It has one instance of the {@link MovingAverageBoatSpeedEstimator} * for each wind boatclass, speed level and rounded angle combination. * * @author Frederik Petersen (D054528) * */ -public class BoatSpeedEstimator implements Serializable { +public class MovingAverageBoatSpeedEstimator implements Serializable { private static final long serialVersionUID = -254184914347332658L; @@ -35,10 +35,7 @@ public class BoatSpeedEstimator implements Serializable { private int dataCount = 0; - /** - * @param useLinearRegression if false mean of wind interval is used, otherwise linear regression - */ - public Speed estimateSpeed(double windSpeed, double angleToTheWind) throws NotEnoughDataHasBeenAddedException { + public Speed estimateSpeed() throws NotEnoughDataHasBeenAddedException { LockUtil.lockForRead(lock); if (dataCount < 1) { throw new NotEnoughDataHasBeenAddedException(); diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/NotEnoughDataHasBeenAddedException.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/NotEnoughDataHasBeenAddedException.java index d15ad5ac066..6b8c54683b8 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/NotEnoughDataHasBeenAddedException.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/NotEnoughDataHasBeenAddedException.java @@ -1,5 +1,6 @@ package com.sap.sailing.polars.regression; + public class NotEnoughDataHasBeenAddedException extends Exception { private static final long serialVersionUID = 4134116458606258315L; @@ -12,4 +13,8 @@ public class NotEnoughDataHasBeenAddedException extends Exception { super(string); } + public NotEnoughDataHasBeenAddedException(String string, Throwable cause) { + super(string, cause); + } + } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java index 32f8dd8a7b4..6bf8e1b1e5f 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/regression/impl/IncrementalAnyOrderLeastSquaresImpl.java @@ -6,8 +6,11 @@ import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.math.analysis.polynomials.PolynomialFunction; import org.apache.commons.math.linear.LUDecompositionImpl; import org.apache.commons.math.linear.MatrixUtils; +import org.apache.commons.math.linear.MatrixVisitorException; import org.apache.commons.math.linear.RealMatrix; +import org.apache.commons.math.linear.RealMatrixPreservingVisitor; import org.apache.commons.math.linear.RealVector; +import org.apache.commons.math.linear.SingularMatrixException; import com.sap.sailing.polars.regression.IncrementalLeastSquares; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; @@ -25,6 +28,31 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock; * */ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSquares { + + private class NaNCheckerVisitor implements RealMatrixPreservingVisitor { + + private boolean hasNaNEntry = false; + + @Override + public void visit(int arg0, int arg1, double arg2) throws MatrixVisitorException { + if (Double.isNaN(arg2)) { + hasNaNEntry = true; + } + } + + @Override + public void start(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {} + + @Override + public double end() { + return 0.0; + } + + public boolean hasNaNEntry() { + return hasNaNEntry; + } + + } private double[][] matrixOfXSums; @@ -122,7 +150,7 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua return resultFunction; } - private PolynomialFunction createEstimatingPolynomialFunction() { + private PolynomialFunction createEstimatingPolynomialFunction() throws NotEnoughDataHasBeenAddedException { PolynomialFunction resultFunction; RealMatrix matrixOfXSumsCopy; RealVector vectorOfXYMultSumsCopy; @@ -139,7 +167,18 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua inversedMatrix = type.inverseMatrix(matrixOfXSumsCopy); } if (inversedMatrix == null) { - inversedMatrix = new LUDecompositionImpl(matrixOfXSumsCopy).getSolver().getInverse(); + try { + inversedMatrix = new LUDecompositionImpl(matrixOfXSumsCopy).getSolver().getInverse(); + } catch (SingularMatrixException singularMatrixException) { + throw new NotEnoughDataHasBeenAddedException("Matrix singular, all x input equal?", singularMatrixException); + } + } else { + NaNCheckerVisitor nanChecker = new NaNCheckerVisitor(); + inversedMatrix.walkInOptimizedOrder(nanChecker); + if (nanChecker.hasNaNEntry()) { + throw new NotEnoughDataHasBeenAddedException("Matrix singular, all x input equal?"); + } + } RealVector coeffs = inversedMatrix.operate(vectorOfXYMultSumsCopy); if (!hasIntercept) { @@ -248,10 +287,10 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua */ private static LeastSquaresInversionHelperType getType(int order, boolean hasIntercept) { LeastSquaresInversionHelperType result = null; - switch(order) { - case 3: - result = hasIntercept ? CUBIC : CUBIC_NO_INTERCEPT; - break; + switch (order) { + case 3: + result = hasIntercept ? CUBIC : CUBIC_NO_INTERCEPT; + break; } return result; } From 6375e5f6cc1185173551513ed9bdedf18bd94af9 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Thu, 19 Feb 2015 15:29:30 +0100 Subject: [PATCH 158/264] Since polar data mining now works differently ignore one test until reworked --- .../com/sap/sailing/polars/mining/test/PolarDataMinerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java index 5c600eab0cb..c8acd31d067 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/mining/test/PolarDataMinerTest.java @@ -20,6 +20,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeoutException; +import org.junit.Ignore; import org.junit.Test; import com.sap.sailing.domain.base.BoatClass; @@ -73,6 +74,7 @@ public class PolarDataMinerTest { false, 3); } + @Ignore @Test public void testGrouping() throws InterruptedException, TimeoutException, NoSuchMethodException, NotEnoughDataHasBeenAddedException { From 1a4379c883f5ead3254e7de19c55cca7cf1671f4 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 19 Feb 2015 15:32:39 +0100 Subject: [PATCH 159/264] removed unused dependency in gwt.theme project --- java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF index ff236c0cf0d..cd778356281 100644 --- a/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF @@ -12,7 +12,6 @@ Import-Package: org.osgi.framework;version="1.7.0", javax.servlet.descriptor;version="3.1.0", javax.servlet.http;version="3.1.0" Require-Bundle: com.google.gwt.osgi;bundle-version="2.7.0", - com.sap.sailing.domain.common, com.sap.sse.common, com.sap.sse.gwt Web-ContextPath: /theme From 118cc09dd5c88d12c106fd14dac9dc630c831978 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 19 Feb 2015 16:00:51 +0100 Subject: [PATCH 160/264] removed another unused dependency in gwt.theme project --- java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF index cd778356281..b94daa9dc2a 100644 --- a/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF @@ -12,7 +12,6 @@ Import-Package: org.osgi.framework;version="1.7.0", javax.servlet.descriptor;version="3.1.0", javax.servlet.http;version="3.1.0" Require-Bundle: com.google.gwt.osgi;bundle-version="2.7.0", - com.sap.sse.common, - com.sap.sse.gwt + com.sap.sse.common Web-ContextPath: /theme Export-Package: com.sap.sse.gwt.theme.client From 8117c1a08cfc3bcabc2880a3ec2160f3f8dcc475 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 19 Feb 2015 16:06:17 +0100 Subject: [PATCH 161/264] removed another dependency in gwt.theme project --- java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF index cd778356281..b94daa9dc2a 100644 --- a/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt.theme/META-INF/MANIFEST.MF @@ -12,7 +12,6 @@ Import-Package: org.osgi.framework;version="1.7.0", javax.servlet.descriptor;version="3.1.0", javax.servlet.http;version="3.1.0" Require-Bundle: com.google.gwt.osgi;bundle-version="2.7.0", - com.sap.sse.common, - com.sap.sse.gwt + com.sap.sse.common Web-ContextPath: /theme Export-Package: com.sap.sse.gwt.theme.client From a157a98b1ac518e1e14806997954b9f9695f0ef0 Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 19 Feb 2015 16:07:40 +0100 Subject: [PATCH 162/264] removed unused dependency in sse.common --- java/com.sap.sse.common/META-INF/MANIFEST.MF | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sse.common/META-INF/MANIFEST.MF b/java/com.sap.sse.common/META-INF/MANIFEST.MF index 22a6396d68e..314738417f8 100644 --- a/java/com.sap.sse.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.common/META-INF/MANIFEST.MF @@ -12,5 +12,4 @@ Export-Package: com.sap.sse.common, com.sap.sse.common.scalablevalue, com.sap.sse.common.search, com.sap.sse.common.util -Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0" Bundle-ActivationPolicy: lazy From 50bd80e2fd51cd88fd33d7c3844558427e5c821b Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 19 Feb 2015 16:14:02 +0100 Subject: [PATCH 163/264] readded removed dependency, but found a cyclic dependency this way --- java/com.sap.sse.common/META-INF/MANIFEST.MF | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sse.common/META-INF/MANIFEST.MF b/java/com.sap.sse.common/META-INF/MANIFEST.MF index 314738417f8..22a6396d68e 100644 --- a/java/com.sap.sse.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.common/META-INF/MANIFEST.MF @@ -12,4 +12,5 @@ Export-Package: com.sap.sse.common, com.sap.sse.common.scalablevalue, com.sap.sse.common.search, com.sap.sse.common.util +Require-Bundle: com.sap.sse.datamining.shared;bundle-version="1.0.0" Bundle-ActivationPolicy: lazy From 05f067e5ad183e60d77495397876149e36cc124d Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 19 Feb 2015 16:19:10 +0100 Subject: [PATCH 164/264] a try to further reduce the dependencies in gwt.theme project --- .../src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml index 8af42f07f54..261ba9709d6 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml @@ -5,11 +5,14 @@ + - + From 16735b26d6b469a8d60bff97f05bac142fbf85ad Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Thu, 19 Feb 2015 16:24:11 +0100 Subject: [PATCH 165/264] Adjusted thread pool size for polar data service --- .../src/com/sap/sailing/polars/mining/PolarDataMiner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index fd9a5504c6c..dd90c0b011a 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -40,7 +40,7 @@ import com.sap.sse.datamining.impl.components.ParallelMultiDimensionsValueNestin public class PolarDataMiner { - private static final int THREAD_POOL_SIZE = Math.min(Runtime.getRuntime().availableProcessors(), 3); + private static final int THREAD_POOL_SIZE = Math.max((int) (Runtime.getRuntime().availableProcessors() * (3.0/4.0)), 3); private static final ThreadPoolExecutor executor = createExecutor(); private final PolarSheetGenerationSettings backendPolarSheetGenerationSettings; From c4c4f2c35760b32f4fb12995fd2a121aea135665 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Thu, 19 Feb 2015 16:30:59 +0100 Subject: [PATCH 166/264] Show regression 0 to 30 in diagram --- .../sailing/gwt/ui/server/SailingServiceImpl.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 42345ee1ecf..f8cd74a9f68 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -5314,7 +5314,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S movingAverageSpeedDataLists.get(new Pair(legType, tack)).add( new Pair(windInKnots, averageUpwindStarboardMovingAverage.getObject() .getKnots())); + + averageConfidenceDataLists.get(new Pair(legType, tack)).add( + new Pair(windInKnots, averageUpwindStarboardMovingAverage + .getConfidence())); + + } catch (NotEnoughDataHasBeenAddedException e) { + // Do not add a point to the result + } + + try { SpeedWithBearingWithConfidence averageUpwindStarboardRegression = getService() .getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), legType, tack, true); @@ -5322,10 +5332,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S regressionSpeedDataLists.get(new Pair(legType, tack)).add( new Pair(windInKnots, averageUpwindStarboardRegression.getObject() .getKnots())); - - averageConfidenceDataLists.get(new Pair(legType, tack)).add( - new Pair(windInKnots, averageUpwindStarboardMovingAverage - .getConfidence())); } catch (NotEnoughDataHasBeenAddedException e) { // Do not add a point to the result } From 7e26237f75092cbd990cf1b5576316162394553a Mon Sep 17 00:00:00 2001 From: fmittag Date: Thu, 19 Feb 2015 16:54:53 +0100 Subject: [PATCH 167/264] reduced dependencies in gwt.theme project --- .../src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml index 261ba9709d6..a79745fe37b 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/SSETheme.gwt.xml @@ -5,14 +5,7 @@ - - From c675fea0e8a2a2210e776bf0b14d58712a45a27a Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Thu, 19 Feb 2015 17:11:39 +0100 Subject: [PATCH 168/264] Wind estimation through polars will now use regressions where available --- .../polars/impl/PolarDataServiceImpl.java | 37 +------------- .../mining/AngleAndSpeedRegression.java | 31 ++++++++++++ .../CubicRegressionPerCourseProcessor.java | 15 +++++- .../sailing/polars/mining/PolarDataMiner.java | 49 ++++++++++++++++++- 4 files changed, 92 insertions(+), 40 deletions(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java index 13d769aa4e9..d5ccfa7f6c5 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java @@ -1,6 +1,5 @@ package com.sap.sailing.polars.impl; -import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -9,7 +8,6 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.base.SpeedWithConfidence; -import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.ManeuverType; @@ -17,8 +15,6 @@ import com.sap.sailing.domain.common.PolarSheetGenerationSettings; import com.sap.sailing.domain.common.PolarSheetsData; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.polars.PolarDataService; @@ -55,38 +51,7 @@ public class PolarDataServiceImpl implements PolarDataService { @Override public Set> getAverageTrueWindSpeedAndAngleCandidates(BoatClass boatClass, Speed speedOverGround, LegType legType, Tack tack) { - - // The following is an estimation function. It only serves as a stub. It's the same for all boatclasses and returns - // default maneuver angles. This is NOT final. - // The function is able to return boat speed values for windspeed alues between 5kn and 25kn , which are some kind of realistic - // for sailing boats. They are taken from the 505 polars we gathered in the races until now. - // TODO keep a cache of sampling points over which to fit a function for every boat class and then return the - // corresponding angle - - Set> resultSet = new HashSet<>(); - final int tackFactor = (tack.equals(Tack.PORT)) ? -1 : 1; - if (legType.equals(LegType.UPWIND)) { - CubicEquation upWindEquation = new CubicEquation(0.0002, -0.0245, 0.7602, -0.0463-speedOverGround.getKnots()); - int angle = 49 * tackFactor; - solveAndAddResults(resultSet, upWindEquation, angle); - } else if (legType.equals(LegType.DOWNWIND)) { - CubicEquation downWindEquation = new CubicEquation(0.0003, -0.0373, 1.5213, -2.1309-speedOverGround.getKnots()); - int angle = 30 * tackFactor; - solveAndAddResults(resultSet, downWindEquation, angle); - } - return resultSet; - //return polarDataMiner.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); - } - - private void solveAndAddResults(Set> result, CubicEquation equation, int angle) { - double[] windSpeedCandidates = equation.solve(); - for (int i = 0; i < windSpeedCandidates.length; i++) { - double windSpeedCandidateInKnots = windSpeedCandidates[i] > 0 ? windSpeedCandidates[i] : 0; - if (windSpeedCandidateInKnots < 40) { - result.add(new SpeedWithBearingWithConfidenceImpl(new KnotSpeedWithBearingImpl( - windSpeedCandidateInKnots, new DegreeBearingImpl(angle)), 0.00001, null)); - } - } + return polarDataMiner.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); } @Override diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java index ac91f0dc6b4..414fcf71e22 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java @@ -1,5 +1,8 @@ package com.sap.sailing.polars.mining; +import java.util.HashSet; +import java.util.Set; + import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; import com.sap.sailing.domain.common.Bearing; @@ -10,6 +13,7 @@ import com.sap.sailing.domain.common.confidence.BearingWithConfidence; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.polars.impl.CubicEquation; import com.sap.sailing.polars.regression.IncrementalLeastSquares; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImpl; @@ -37,4 +41,31 @@ public class AngleAndSpeedRegression { return new SpeedWithBearingWithConfidenceImpl(speedWithBearing, /*FIXME*/ 0.5, null); } + public Set> estimateTrueWindSpeedAndAngleCandidates(Speed speedOverGround) throws NotEnoughDataHasBeenAddedException { + double[] coefficiants = speedRegression.getOrCreatePolynomialFunction().getCoefficients(); + CubicEquation equation = new CubicEquation(coefficiants[2], coefficiants[1], coefficiants[0], -speedOverGround.getKnots()); + + double[] windSpeedCandidates = equation.solve(); + Set> result = new HashSet<>(); + for (int i = 0; i < windSpeedCandidates.length; i++) { + double windSpeedCandidateInKnots = windSpeedCandidates[i]; + if (windSpeedCandidateInKnots >= 0) { + double angle = 0; + boolean angleFound; + try { + angle = angleRegression.getOrCreatePolynomialFunction().value(windSpeedCandidateInKnots); + angleFound = true; + } catch(NotEnoughDataHasBeenAddedException e) { + angleFound = false; + } + if (angleFound) { + result.add(new SpeedWithBearingWithConfidenceImpl(new KnotSpeedWithBearingImpl( + windSpeedCandidateInKnots, new DegreeBearingImpl(angle)), 0.00001, null)); + } + } + } + + return result; + } + } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java index 39b4e94a5b2..404a00de67e 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java @@ -1,6 +1,7 @@ package com.sap.sailing.polars.mining; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Logger; @@ -51,8 +52,18 @@ public class CubicRegressionPerCourseProcessor implements Processor> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass, Speed speedOverGround, LegType legType, Tack tack) { - // TODO Auto-generated method stub - return null; + GroupKey key = createGroupKey(boatClass, legType, tack); + Set> result = new HashSet<>(); + if (regressions.containsKey(key)) { + try { + result = regressions.get(key).estimateTrueWindSpeedAndAngleCandidates(speedOverGround); + } catch (NotEnoughDataHasBeenAddedException e) { + // Return empty result + } + } else { + // Return empty result; + } + return result; } private GroupKey createGroupKey(final BoatClass boatClass, final LegType legType, diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index dd90c0b011a..5ce57c498c2 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; @@ -14,6 +15,7 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.base.SpeedWithConfidence; +import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.LegType; @@ -24,12 +26,14 @@ import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl; import com.sap.sailing.domain.common.impl.PolarSheetsDataImpl; import com.sap.sailing.domain.common.impl.PolarSheetsHistogramDataImpl; import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance; import com.sap.sailing.domain.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.polars.impl.CubicEquation; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.data.ClusterGroup; @@ -168,7 +172,48 @@ public class PolarDataMiner { public Set> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass, Speed speedOverGround, LegType legType, Tack tack) { - return cubicRegressionPerCourseProcessor.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); + Set> resultSet = cubicRegressionPerCourseProcessor.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); + if (resultSet.isEmpty()) { + // FALLBACK function if no data was available + resultSet = getAverageTrueWindSpeedAndAngleCandidatesWithFallbackFunction(boatClass, speedOverGround, legType, tack); + } + return resultSet; + } + + private Set> getAverageTrueWindSpeedAndAngleCandidatesWithFallbackFunction(BoatClass boatClass, + Speed speedOverGround, LegType legType, Tack tack) { + + // The following is an estimation function. It only serves as a stub. It's the same for all boatclasses and returns + // default maneuver angles. This is NOT final. + // The function is able to return boat speed values for windspeed alues between 5kn and 25kn , which are some kind of realistic + // for sailing boats. They are taken from the 505 polars we gathered in the races until now. + // TODO keep a cache of sampling points over which to fit a function for every boat class and then return the + // corresponding angle + + Set> resultSet = new HashSet<>(); + final int tackFactor = (tack.equals(Tack.PORT)) ? -1 : 1; + if (legType.equals(LegType.UPWIND)) { + CubicEquation upWindEquation = new CubicEquation(0.0002, -0.0245, 0.7602, -0.0463-speedOverGround.getKnots()); + int angle = 49 * tackFactor; + solveAndAddResults(resultSet, upWindEquation, angle); + } else if (legType.equals(LegType.DOWNWIND)) { + CubicEquation downWindEquation = new CubicEquation(0.0003, -0.0373, 1.5213, -2.1309-speedOverGround.getKnots()); + int angle = 30 * tackFactor; + solveAndAddResults(resultSet, downWindEquation, angle); + } + return resultSet; + //return polarDataMiner.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack); + } + + private void solveAndAddResults(Set> result, CubicEquation equation, int angle) { + double[] windSpeedCandidates = equation.solve(); + for (int i = 0; i < windSpeedCandidates.length; i++) { + double windSpeedCandidateInKnots = windSpeedCandidates[i] > 0 ? windSpeedCandidates[i] : 0; + if (windSpeedCandidateInKnots < 40) { + result.add(new SpeedWithBearingWithConfidenceImpl(new KnotSpeedWithBearingImpl( + windSpeedCandidateInKnots, new DegreeBearingImpl(angle)), 0.00001, null)); + } + } } public PolarSheetsData createFullSheetForBoatClass(BoatClass boatClass) { @@ -196,7 +241,7 @@ public class PolarDataMiner { } averagedPolarDataByWindSpeed[windIndex][angle] = speedWithConfidence.getObject().getKnots(); - int dataCount = 0; /*FIXME*/ + int dataCount = 200; /*FIXME*/ totalDataCount = totalDataCount + dataCount; // FIXME hard coded From 8ed5f815254c28569e1e47aacf85b747bd6079ad Mon Sep 17 00:00:00 2001 From: fmittag Date: Sun, 22 Feb 2015 12:37:29 +0100 Subject: [PATCH 169/264] added (empty) image and video gallery to the theme library for ubilabs --- .../component/imagegallery/ImageGallery.gss | 2 ++ .../component/imagegallery/ImageGallery.java | 19 +++++++++++++++ .../imagegallery/ImageGallery.ui.xml | 9 ++++++++ .../imagegallery/ImageGalleryResources.java | 16 +++++++++++++ ...dingIndicator.css => LoadingIndicator.gss} | 0 .../loadingindicator/LoadingIndicator.ui.xml | 4 ++-- .../LoadingIndicatorResources.java | 2 +- .../{SAPHeader.css => SAPHeader.gss} | 1 - .../sapheader/SAPHeaderResources.java | 2 +- .../component/videogallery/VideoGallery.gss | 2 ++ .../component/videogallery/VideoGallery.java | 19 +++++++++++++++ .../videogallery/VideoGallery.ui.xml | 9 ++++++++ .../videogallery/VideoGalleryResources.java | 16 +++++++++++++ .../gwt/theme/client/showcase/MainPage.ui.xml | 6 +++++ .../components/ImageGalleryShowcase.java | 23 +++++++++++++++++++ .../components/ImageGalleryShowcase.ui.xml | 20 ++++++++++++++++ .../components/VideoGalleryShowcase.java | 23 +++++++++++++++++++ .../components/VideoGalleryShowcase.ui.xml | 20 ++++++++++++++++ 18 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.gss create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGalleryResources.java rename java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/{LoadingIndicator.css => LoadingIndicator.gss} (100%) rename java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/{SAPHeader.css => SAPHeader.gss} (96%) create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.gss create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGalleryResources.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.ui.xml create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.java create mode 100644 java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.ui.xml diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.gss new file mode 100644 index 00000000000..e46fdf65acd --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.gss @@ -0,0 +1,2 @@ +.sampleCssClass { +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.java new file mode 100644 index 00000000000..3eb8d4bd577 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.java @@ -0,0 +1,19 @@ +package com.sap.sse.gwt.theme.client.component.imagegallery; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class ImageGallery extends Composite { + private static ImageGalleryUiBinder uiBinder = GWT.create(ImageGalleryUiBinder.class); + + interface ImageGalleryUiBinder extends UiBinder { + } + + public ImageGallery() { + ImageGalleryResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.ui.xml new file mode 100644 index 00000000000..a98b61b8104 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGallery.ui.xml @@ -0,0 +1,9 @@ + + + + + + Image gallery... + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGalleryResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGalleryResources.java new file mode 100644 index 00000000000..b51de3ceda3 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/imagegallery/ImageGalleryResources.java @@ -0,0 +1,16 @@ +package com.sap.sse.gwt.theme.client.component.imagegallery; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface ImageGalleryResources extends ClientBundle { + public static final ImageGalleryResources INSTANCE = GWT.create(ImageGalleryResources.class); + + @Source("ImageGallery.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String sampleCssClass(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.css b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.gss similarity index 100% rename from java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.css rename to java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.gss diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml index 506bb27b6de..2e51dbe3527 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicator.ui.xml @@ -2,8 +2,8 @@ - - + + Loading... diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java index 683743fdc7d..4e3672bb46c 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/loadingindicator/LoadingIndicatorResources.java @@ -7,7 +7,7 @@ import com.google.gwt.resources.client.CssResource; public interface LoadingIndicatorResources extends ClientBundle { public static final LoadingIndicatorResources INSTANCE = GWT.create(LoadingIndicatorResources.class); - @Source("LoadingIndicator.css") + @Source("LoadingIndicator.gss") LocalCss css(); public interface LocalCss extends CssResource { diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.css b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.gss similarity index 96% rename from java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.css rename to java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.gss index 9de247011bb..e5753a2d9cd 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.css +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeader.gss @@ -1,5 +1,4 @@ .siteheader { - font-size: 15px; font-size: 1rem; background: #333; border-bottom: 5px solid #f0ab00; diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java index cea0a1754d1..dbc04cc3d51 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/sapheader/SAPHeaderResources.java @@ -8,7 +8,7 @@ import com.google.gwt.resources.client.ImageResource; public interface SAPHeaderResources extends ClientBundle { public static final SAPHeaderResources INSTANCE = GWT.create(SAPHeaderResources.class); - @Source("SAPHeader.css") + @Source("SAPHeader.gss") LocalCss css(); public interface LocalCss extends CssResource { diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.gss b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.gss new file mode 100644 index 00000000000..e46fdf65acd --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.gss @@ -0,0 +1,2 @@ +.sampleCssClass { +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.java new file mode 100644 index 00000000000..c197472e8a3 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.java @@ -0,0 +1,19 @@ +package com.sap.sse.gwt.theme.client.component.videogallery; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class VideoGallery extends Composite { + private static VideoGalleryUiBinder uiBinder = GWT.create(VideoGalleryUiBinder.class); + + interface VideoGalleryUiBinder extends UiBinder { + } + + public VideoGallery() { + VideoGalleryResources.INSTANCE.css().ensureInjected(); + + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.ui.xml new file mode 100644 index 00000000000..a98b61b8104 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGallery.ui.xml @@ -0,0 +1,9 @@ + + + + + + Image gallery... + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGalleryResources.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGalleryResources.java new file mode 100644 index 00000000000..3bc08cc64e2 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/component/videogallery/VideoGalleryResources.java @@ -0,0 +1,16 @@ +package com.sap.sse.gwt.theme.client.component.videogallery; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface VideoGalleryResources extends ClientBundle { + public static final VideoGalleryResources INSTANCE = GWT.create(VideoGalleryResources.class); + + @Source("VideoGallery.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String sampleCssClass(); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml index bd21ccd17ee..3dedb3af0cd 100644 --- a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/MainPage.ui.xml @@ -14,6 +14,12 @@ + + + + + + diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.java new file mode 100644 index 00000000000..aee0647ff08 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.java @@ -0,0 +1,23 @@ +package com.sap.sse.gwt.theme.client.showcase.components; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.imagegallery.ImageGallery; + +public class ImageGalleryShowcase extends Composite { + + private static ImageGalleryShowcaseUiBinder uiBinder = GWT.create(ImageGalleryShowcaseUiBinder.class); + + interface ImageGalleryShowcaseUiBinder extends UiBinder { + } + + @UiField + ImageGallery imageGallery; + + public ImageGalleryShowcase() { + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.ui.xml new file mode 100644 index 00000000000..018e825f9e1 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/ImageGalleryShowcase.ui.xml @@ -0,0 +1,20 @@ + + + + + + + An image gallery. +

      Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.imagegallery"

      + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.java b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.java new file mode 100644 index 00000000000..41d1ebc4a22 --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.java @@ -0,0 +1,23 @@ +package com.sap.sse.gwt.theme.client.showcase.components; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.theme.client.component.videogallery.VideoGallery; + +public class VideoGalleryShowcase extends Composite { + + private static VideoGalleryShowcaseUiBinder uiBinder = GWT.create(VideoGalleryShowcaseUiBinder.class); + + interface VideoGalleryShowcaseUiBinder extends UiBinder { + } + + @UiField + VideoGallery videoGallery; + + public VideoGalleryShowcase() { + initWidget(uiBinder.createAndBindUi(this)); + } +} diff --git a/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.ui.xml b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.ui.xml new file mode 100644 index 00000000000..cf7212aaced --- /dev/null +++ b/java/com.sap.sse.gwt.theme/src/main/java/com/sap/sse/gwt/theme/client/showcase/components/VideoGalleryShowcase.ui.xml @@ -0,0 +1,20 @@ + + + + + + + A video gallery. +

      Import: xmlns:sap="urn:import:com.sap.sse.gwt.theme.client.component.videogallery"

      +
      +
      + + + + + +
      +
      \ No newline at end of file From c8be00dc035b1ce2849d37374d76f524c826cf93 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 23 Feb 2015 09:50:24 +0100 Subject: [PATCH 170/264] added theme/WEB-INF/classes/.dummy to force creation on clients, avoiding warnings and build errors due to missing directory --- java/com.sap.sse.gwt.theme/WEB-INF/.gitignore | 1 - java/com.sap.sse.gwt.theme/WEB-INF/classes/.dummy | 0 2 files changed, 1 deletion(-) create mode 100755 java/com.sap.sse.gwt.theme/WEB-INF/classes/.dummy diff --git a/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore b/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore index b30e141e749..8541464a78c 100644 --- a/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore +++ b/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore @@ -1,2 +1 @@ deploy -classes \ No newline at end of file diff --git a/java/com.sap.sse.gwt.theme/WEB-INF/classes/.dummy b/java/com.sap.sse.gwt.theme/WEB-INF/classes/.dummy new file mode 100755 index 00000000000..e69de29bb2d From bcd59b9b2a6065fbbe43dfe2d113160e5c82c960 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 23 Feb 2015 09:52:00 +0100 Subject: [PATCH 171/264] added classes directory to .gitignore again because we don't want to version build products --- java/com.sap.sse.gwt.theme/WEB-INF/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore b/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore index 8541464a78c..46b0c4dbcf4 100644 --- a/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore +++ b/java/com.sap.sse.gwt.theme/WEB-INF/.gitignore @@ -1 +1,2 @@ deploy +classes From 67d1fa1a70fa022e12a9855beded0f823be74a3f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 23 Feb 2015 11:09:19 +0100 Subject: [PATCH 172/264] removed the sap3sse package that was accidentally created in commit 25726e99e6eecabf00932f0c37aba285d8724313 --- .../SSETheme_GWT sdm.launch | 54 ++-- .../com.sap.sse.gwt.theme/SSETheme_GWT.launch | 52 ++-- .../busyindicator/BusyIndicatorResources.java | 10 - .../busyindicator/SimpleBusyIndicator.java | 44 --- .../controls/carousel/ImageCarousel.java | 267 ------------------ 5 files changed, 53 insertions(+), 374 deletions(-) delete mode 100644 java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java delete mode 100644 java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java delete mode 100644 java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/carousel/ImageCarousel.java diff --git a/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch b/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch index e61889df28b..71c80a9b61b 100644 --- a/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch +++ b/java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch @@ -1,27 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch b/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch index 497ba323966..099018ea63b 100644 --- a/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch +++ b/java/com.sap.sse.gwt.theme/SSETheme_GWT.launch @@ -1,26 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java b/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java deleted file mode 100644 index 1add987b8c5..00000000000 --- a/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.sap.sse.gwt.client.controls.busyindicator; - -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.ImageResource; - -public interface BusyIndicatorResources extends ClientBundle { - - @Source("busy_indicator_circle.gif") - ImageResource busyIndicatorCircle(); -} diff --git a/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java b/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java deleted file mode 100644 index 5a607e233d6..00000000000 --- a/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.sap.sse.gwt.client.controls.busyindicator; - -import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.ui.Image; - -public class SimpleBusyIndicator extends BusyIndicator { - - private Image busyIndicator; - - /** - * Creates a new SimpleBusyIndicator with the busy state false.
      - * The busy indicator component is a circling GIF. - */ - public SimpleBusyIndicator() { - this(false, 1.0f); - } - - /** - * Creates a new SimpleBusyIndicator with a custom busy state.
      - * The busy indicator component is a circling GIF. - * - * @param busy Sets the busy state of the BusyIndicator - * @param scale Scales the displayed image. 1.0 is 100%, 0.50 is 50%, ... - */ - public SimpleBusyIndicator(boolean busy, float scale) { - this(busy, scale, STYLE_NAME_PREFIX + "Simple", STYLE_NAME_PREFIX + "Circle"); - } - - public SimpleBusyIndicator(boolean busy, float scale, String panelCssClass, String imageCssClass) { - this.setStyleName(panelCssClass); - ImageResource resource = RESOURCES.busyIndicatorCircle(); - busyIndicator = new Image(resource.getSafeUri()); - busyIndicator.setStyleName(imageCssClass); - busyIndicator.setPixelSize((int) (resource.getWidth() * scale), (int) (resource.getHeight() * scale)); - add(busyIndicator); - setBusy(busy); - } - - @Override - public void setBusy(boolean busy) { - super.setBusy(busy); - busyIndicator.setVisible(busy); - } -} diff --git a/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/carousel/ImageCarousel.java b/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/carousel/ImageCarousel.java deleted file mode 100644 index a5456c8f101..00000000000 --- a/java/com.sap.sse.gwt/src/com/sap3sse/gwt/client/controls/carousel/ImageCarousel.java +++ /dev/null @@ -1,267 +0,0 @@ -package com.sap.sse.gwt.client.controls.carousel; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.ImageElement; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.safehtml.shared.UriUtils; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.Widget; - -/** - * Image carousel that uses the open source "slick carousel". - *

      - * Documentation/ download of slick js and css from homepate/ github page. - * - * @see Slick homepage. - * @see Slick github page. - * - * - *

      - * - * This class uses JSNI to wrap the JS slick carousel. - * - * Usage: - * - * - Create widget - * - * ImageCarousel slider = new ImageCarousel(); - * - * - Add images - * - * slider.addImage("http://demotivators.despair.com/demotivational/foresightdemotivator.jpg"); - * slider.addImage("http://demotivators.despair.com/demotivational/commitmentdemotivator.jpg"); - * slider.addImage("http://demotivators.despair.com/demotivational/platitudesdemotivator.jpg"); - * slider.addImage("http://demotivators.despair.com/demotivational/destinyrockdemotivator.jpg"); - * slider.addImage("http://demotivators.despair.com/demotivational/aspirationdemotivator.jpg"); - * - * - Start the carousel - * - * slider.init(); - * - * The widget depends on css and js resources, as well as jquery 1.7+. - * - * - * Created by pgtaboada on 10.11.14. - */ -public class ImageCarousel extends Widget { - - private static SlickSliderUiBinder ourUiBinder = GWT.create(SlickSliderUiBinder.class); - - /** - * slick slider property: dots - */ - private boolean showDots = true; - /** - * slick slider property: centerMode - */ - private boolean centerMode = true; - /** - * slick slider property: variableWidth - */ - private boolean variableWidth = true; - /** - * slick slider property: infiniteScrolling - */ - private boolean infiniteScrolling = true; - /** - * Image margin (effectively sets spacing between images) - */ - private int contentMarginInPixels = 10; - /** - * Lazy load mechanism to use - */ - private String lazyload = LAZYLOAD.ONDEMAND.getPropertyValue(); - - /** - * The height of the images - */ - private int imagesHeight = 300; - - private final String uniqueId; - - /** - * Widget constructor - */ - public ImageCarousel() { - setElement(ourUiBinder.createAndBindUi(this)); - - uniqueId = "slider_" + Document.get().createUniqueId(); - getElement().addClassName(uniqueId); - init(); - } - - /** - * JSNI wrapper that does setup the slider - * - * @param uniqueId - */ - native void setupSlider(ImageCarousel sliderReference) /*-{ - - $wnd - .$( - '.' - + (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::uniqueId)) - .slick( - { - dots : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::showDots), - infinite : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::infiniteScrolling), - centerMode : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::centerMode), - variableWidth : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::variableWidth), - lazyLoad : (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::lazyload), - swipeToSlide : false, - arrows : true, - responsive : true, - slidesToShow : 2 - - }); - $wnd - .$( - '.' - + (sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::uniqueId) - + '>.slick-list>.slick-track') - .height( - sliderReference.@com.sap.sse.gwt.client.controls.carousel.ImageCarousel::imagesHeight - + 'px'); - }-*/; - - /** - * Add new image to carousel. - * - * @param url - */ - public void addImage(String url, int height, int width) { - - DivElement imageHolder = Document.get().createDivElement(); - ImageElement imageElement = Document.get().createImageElement(); - imageElement.setAttribute("data-lazy", UriUtils.fromString(url).asString()); - - imageHolder.getStyle().setHeight(imagesHeight, Unit.PX); - imageHolder.getStyle().setWidth(Math.round(width * (imagesHeight / (double) height)), Unit.PX); - imageHolder.appendChild(imageElement); - - getElement().appendChild(imageHolder); - - if (getElement().getChildCount() > 20) { - setInfiniteScrolling(false); - setShowDots(false); - } - - } - - /** - * Define spacing between images in carousel. In fact, it is setting the img margins in pixels. - * - * @param contentMarginInPixels - */ - public void setContentMarginInPixels(int contentMarginInPixels) { - this.contentMarginInPixels = contentMarginInPixels; - } - - /** - * Set lazy loading technique. Accepts 'ondemand' or 'progressive'. - * - * @param lazyload - */ - public void setLazyload(LAZYLOAD lazyload) { - this.lazyload = lazyload.getPropertyValue(); - } - - /** - * Enable infinite loop sliding - */ - public void setInfiniteScrolling(boolean infiniteScrolling) { - this.infiniteScrolling = infiniteScrolling; - } - - /** - * Enable variable width slides. - * - * @param variableWidth - */ - public void setVariableWidth(boolean variableWidth) { - this.variableWidth = variableWidth; - } - - /** - * Enables centered view with partial prev/next slides. - * - * @param centerMode - */ - public void setCenterMode(boolean centerMode) { - this.centerMode = centerMode; - } - - /** - * Enable dot indicators - * - * @param showDots - */ - public void setShowDots(boolean showDots) { - this.showDots = showDots; - } - - /** - * UiBinder interface - */ - interface SlickSliderUiBinder extends UiBinder { - } - - /** - * Enum wrapper for lazy load techniques - */ - public enum LAZYLOAD { - - /** - * Loads each image "on demand" - */ - ONDEMAND("ondemand"), - - /** - * Progressively loads one image after another - */ - PROGRESSIVE("progressive"); - - String propertyValue; - - LAZYLOAD(String propertyValue) { - this.propertyValue = propertyValue; - } - - public String getPropertyValue() { - return propertyValue; - } - } - - /** - * Apply slick js to constructed dom tree. - */ - private void init() { - final ImageCarousel reference = this; - - Scheduler.get().scheduleDeferred(new Command() { - - @Override - public void execute() { - try { - setupSlider(reference); - - for (int childIndex = 0; childIndex < getElement().getChildCount(); childIndex++) { - Element child = Element.as(getElement().getChild(childIndex)); - if (child.getClassName() != null && child.getClassName().contains("slick-track")) { - - } - } - - } catch (Exception e) { - GWT.log("Catched Exception on slider init", e); - } - } - }); - } - -} \ No newline at end of file From b8cba71dfe6d483eb1fc6657feb1b57842e1cdb7 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 23 Feb 2015 13:16:44 +0100 Subject: [PATCH 173/264] override runTest() method to ensure AsyncActionsExecutioTest is on the call stack, donating its bundle class loader for resolving the gwt.xml module definition --- .../sse/gwt/test/client/AsyncActionsExecutorTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java index e8779f89c46..121b468cfe9 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java @@ -55,7 +55,16 @@ public class AsyncActionsExecutorTest extends GWTTestCase { }; private AsyncActionsExecutor executor; - + + /** + * Delegate to super; this has the effect of putting this class on the stack frame before the module loading takes place. + * Therefore, this class's class loader will be consulted for resolving the GWT module definition. + */ + @Override + public void runTest() { + super.run(); + } + @Test public void testThatANonTriggeringActionLeadsToPendingCall() { executor = new AsyncActionsExecutor(/*maxPendingCalls*/6, /*maxPendingCallsPerType*/5, From 16ce440a97870c3f1f9edf8aa70519fae19c818c Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Mon, 23 Feb 2015 13:23:33 +0000 Subject: [PATCH 174/264] Added a passage about the new curios error, since the update to GWT 2.7 --- wiki/local-gwt.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wiki/local-gwt.md b/wiki/local-gwt.md index 02e687f4165..0690be0ae51 100644 --- a/wiki/local-gwt.md +++ b/wiki/local-gwt.md @@ -28,4 +28,10 @@ One of the great strengths of GWT is the use of Eclipse as a Java source-level d You can set breakpoints in your GWT Java code and inspect values for suspended threads as usual for any Java development. -Note the reduced performance which is largely due to the way the Java VM and the browser plug-in communicate. In particular, many fine-grained changes to the DOM can be quite costly. Therefore, if you're only interested in server-side debugging, consider [compiling](#Local-GWT-Compile) the GWT code for better performance. \ No newline at end of file +Note the reduced performance which is largely due to the way the Java VM and the browser plug-in communicate. In particular, many fine-grained changes to the DOM can be quite costly. Therefore, if you're only interested in server-side debugging, consider [compiling](#Local-GWT-Compile) the GWT code for better performance. + +## Curious Errors + +There's a randomly occuring error, since the update to GWT 2.7, where an entry point fails to load. The Development Mode view of Eclipse shows the error +**Your source appears not to live underneath a subpackage called 'client'; no problem, but you'll need to use the <source> directive in your module to make it accessible** +The problem can be "solved" by retrying until it works. \ No newline at end of file From e6dfe7ea2ce633ba1adaa185a5afc3ccaf57d6e8 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 23 Feb 2015 15:32:22 +0100 Subject: [PATCH 175/264] fixed how permissions are checked for showing admin console widgets --- .../gwt/adminconsole/AdminConsolePanel.java | 36 +++++++++++++++---- .../sap/sse/security/ui/shared/UserDTO.java | 8 ++--- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java index ff67a2bd38d..d9f6cc7f7e8 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java @@ -25,6 +25,7 @@ import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.panels.VerticalTabLayoutPanel; import com.sap.sse.security.shared.Permission; import com.sap.sse.security.shared.PermissionsForRoleProvider; +import com.sap.sse.security.shared.WildcardPermission; import com.sap.sse.security.ui.client.UserService; import com.sap.sse.security.ui.client.UserStatusEventHandler; import com.sap.sse.security.ui.loginpanel.LoginPanel; @@ -288,10 +289,16 @@ public class AdminConsolePanel extends DockLayoutPanel { } /** - * Remembers the tab panel in which the widgetToAdd is to be displayed and which - * permissions are sufficient to see the widget. For the tabPanel, all permissions provided here - * are added to the tab panel's permissions so that the user will see the tab panel as soon as the user may see - * any of the widgets inside that panel + * Remembers the tab panel in which the widgetToAdd is to be displayed and which permissions are + * sufficient to see the widget. For the tabPanel, all permissions provided here are added to the tab + * panel's permissions so that the user will see the tab panel as soon as the user may see any of the widgets inside + * that panel + * + * @param requiresAnyOfThesePermissions + * zero or more permissions; if no permissions are provided, the user will never be able to see the + * widget. Otherwise, if any of these permissions implies any of the permissions the user has, the user + * will be shown the widget. In particular, a "*" wildcard permission will show the widget to all users, + * regardless their actual permissions. */ private void remeberWidgetLocationAndPermissions(VerticalOrHorizontalTabLayoutPanel tabPanel, Widget widgetToAdd, String tabTitle, Permission... requiresAnyOfThesePermissions) { @@ -321,10 +328,25 @@ public class AdminConsolePanel extends DockLayoutPanel { } } + /** + * A user is defined to have permission to see a widget if the widget's required permissions imply any of the permissions the + * user has. This may at first seem the wrong way around. However, the problem is that a widget cannot express a general permission + * that is implied by any detailed permissions. Wildcard permissions don't work this way. Instead, a wildcard permission implies + * detailed permissions. This way, if the widget requires, say, "event:*:*" (or "event" for short), this permission implies + * all more detailed event permissions such as "event:write:9456192873". Therefore, the permissions provided for widgets + * must imply a permission the user has in order for the user to see the tab. More detailed permissions checks can then be + * applied at a more detailed level of the UI and, of course, in the back end. Additionally, if any of the user's permissions + * implies the required permission (e.g., the user having "*" as the administrator's permission), permission to see the widget + * is also implied. + */ private boolean userHasPermissionsToSeeWidget(UserDTO user, Widget widget) { - for (Permission requiredPermission : permissionsAnyOfWhichIsRequiredToSeeWidget.get(widget)) { - if (user.hasPermission(requiredPermission.getStringPermission(), permissionsForRoleProvider)) { - return true; + for (Permission requiredStringPermission : permissionsAnyOfWhichIsRequiredToSeeWidget.get(widget)) { + WildcardPermission requiredPermission = new WildcardPermission(requiredStringPermission.getStringPermission()); + for (String userStringPermission : user.getAllPermissions(permissionsForRoleProvider)) { + WildcardPermission userPermission = new WildcardPermission(userStringPermission); + if (requiredPermission.implies(userPermission) || userPermission.implies(requiredPermission)) { + return true; + } } } return false; diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java index 4dc41bd9f6f..6d7b51d460d 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java @@ -72,18 +72,18 @@ public class UserDTO implements IsSerializable { * permissionForRoleProvider and including the {@link #getStringPermissions()} that are set explicitly * for this user. * - * @param permissionForRoleProvider + * @param permissionsForRoleProvider * may be null in which case only the {@link #getStringPermissions() explicit permissions} * set for this user will be returned. * @return a set of permissions with no duplicates, all in the format parsable by * {@link WildcardPermission#WildcardPermission(String)} */ - public Iterable getAllPermissions(PermissionsForRoleProvider permissionForRoleProvider) { + public Iterable getAllPermissions(PermissionsForRoleProvider permissionsForRoleProvider) { Set result = new LinkedHashSet<>(); Util.addAll(getStringPermissions(), result); - if (permissionForRoleProvider != null) { + if (permissionsForRoleProvider != null) { for (String role : getRoles()) { - Util.addAll(permissionForRoleProvider.getPermissions(role), result); + Util.addAll(permissionsForRoleProvider.getPermissions(role), result); } } return result; From 87620027942611d8d4754e342d81e7d19d09436d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 23 Feb 2015 18:25:11 +0100 Subject: [PATCH 176/264] made all gwt.osgi to gwt.dev dependencies optional --- java/com.google.gwt.osgi/META-INF/MANIFEST.MF | 34 +++++++++---------- .../security/jaxrs/api/SecurityResource.java | 9 ++++- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF index 400e003ca8c..cb85f9ff759 100644 --- a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF @@ -172,22 +172,22 @@ Export-Package: com.google.gwt.activity.shared, com.google.web.bindery.requestfactory.vm.testing, org.hibernate.validator, org.hibernate.validator.engine -Require-Bundle: javax.validation;bundle-version="1.0.0", - org.junit4;bundle-version="4.8.2" -Import-Package: com.gargoylesoftware.htmlunit, - com.google.gwt.core.ext, - com.google.gwt.core.ext.typeinfo, - com.google.gwt.dev, - com.google.gwt.dev.cfg, - com.google.gwt.dev.javac.typemodel, - com.google.gwt.dev.shell, - com.google.gwt.dev.shell.jetty, - com.google.gwt.dev.util.arg, - com.google.gwt.dev.util.collect, - com.google.gwt.junit, - com.google.gwt.thirdparty.guava.common.collect, - com.google.gwt.util.tools, +Require-Bundle: javax.validation;bundle-version="1.0.0";resolution:=optional, + org.junit4;bundle-version="4.8.2";resolution:=optional +Import-Package: com.gargoylesoftware.htmlunit;resolution:=optional, + com.google.gwt.core.ext;resolution:=optional, + com.google.gwt.core.ext.typeinfo;resolution:=optional, + com.google.gwt.dev;resolution:=optional, + com.google.gwt.dev.cfg;resolution:=optional, + com.google.gwt.dev.javac.typemodel;resolution:=optional, + com.google.gwt.dev.shell;resolution:=optional, + com.google.gwt.dev.shell.jetty;resolution:=optional, + com.google.gwt.dev.util.arg;resolution:=optional, + com.google.gwt.dev.util.collect;resolution:=optional, + com.google.gwt.junit;resolution:=optional, + com.google.gwt.thirdparty.guava.common.collect;resolution:=optional, + com.google.gwt.util.tools;resolution:=optional, javax.servlet;version="2.6.0", javax.servlet.http;version="2.6.0", - org.eclipse.jetty.server, - org.eclipse.jetty.webapp + org.eclipse.jetty.server;resolution:=optional, + org.eclipse.jetty.webapp;resolution:=optional diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index 3baf3a3c261..d0122e6a051 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -11,6 +11,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.apache.shiro.SecurityUtils; import org.json.simple.JSONObject; import com.sap.sse.security.User; @@ -29,7 +30,13 @@ public class SecurityResource extends AbstractSecurityResource { @Path("/hello") @Produces("application/json;charset=UTF-8") public Response sayHello() { - return Response.ok("Hello!", MediaType.TEXT_PLAIN).build(); + final String messageText; + if (SecurityUtils.getSubject().isAuthenticated()) { + messageText = "Hello "+SecurityUtils.getSubject().getPrincipal(); + } else { + messageText = "Hello!"; + } + return Response.ok(messageText, MediaType.TEXT_PLAIN).build(); } @POST From 9f2c0b3516498736c048bef4175b257f5840081a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 23 Feb 2015 21:04:02 +0100 Subject: [PATCH 177/264] made javax.servlet dependenciees optional in gwt.osgi; added extraRequirement com.google.gwt.dev to target platform for testing --- java/com.google.gwt.osgi/META-INF/MANIFEST.MF | 4 ++-- java/pom.xml | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF index cb85f9ff759..8827322a846 100644 --- a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF @@ -187,7 +187,7 @@ Import-Package: com.gargoylesoftware.htmlunit;resolution:=optional, com.google.gwt.junit;resolution:=optional, com.google.gwt.thirdparty.guava.common.collect;resolution:=optional, com.google.gwt.util.tools;resolution:=optional, - javax.servlet;version="2.6.0", - javax.servlet.http;version="2.6.0", + javax.servlet;version="2.6.0";resolution:=optional, + javax.servlet.http;version="2.6.0";resolution:=optional, org.eclipse.jetty.server;resolution:=optional, org.eclipse.jetty.webapp;resolution:=optional diff --git a/java/pom.xml b/java/pom.xml index 8748125cac6..f027f41b003 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -261,6 +261,15 @@ ${tycho-version} p2 + + + + eclipse-plugin + com.google.gwt.dev + 0.0.0 + + + ${project.groupId} From 4215ca202dce934c9d2018ddce84c9537335cb8d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 23 Feb 2015 18:25:11 +0100 Subject: [PATCH 178/264] made all gwt.osgi to gwt.dev dependencies optional --- java/com.google.gwt.osgi/META-INF/MANIFEST.MF | 34 +++++++++---------- .../security/jaxrs/api/SecurityResource.java | 9 ++++- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF index 400e003ca8c..cb85f9ff759 100644 --- a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF @@ -172,22 +172,22 @@ Export-Package: com.google.gwt.activity.shared, com.google.web.bindery.requestfactory.vm.testing, org.hibernate.validator, org.hibernate.validator.engine -Require-Bundle: javax.validation;bundle-version="1.0.0", - org.junit4;bundle-version="4.8.2" -Import-Package: com.gargoylesoftware.htmlunit, - com.google.gwt.core.ext, - com.google.gwt.core.ext.typeinfo, - com.google.gwt.dev, - com.google.gwt.dev.cfg, - com.google.gwt.dev.javac.typemodel, - com.google.gwt.dev.shell, - com.google.gwt.dev.shell.jetty, - com.google.gwt.dev.util.arg, - com.google.gwt.dev.util.collect, - com.google.gwt.junit, - com.google.gwt.thirdparty.guava.common.collect, - com.google.gwt.util.tools, +Require-Bundle: javax.validation;bundle-version="1.0.0";resolution:=optional, + org.junit4;bundle-version="4.8.2";resolution:=optional +Import-Package: com.gargoylesoftware.htmlunit;resolution:=optional, + com.google.gwt.core.ext;resolution:=optional, + com.google.gwt.core.ext.typeinfo;resolution:=optional, + com.google.gwt.dev;resolution:=optional, + com.google.gwt.dev.cfg;resolution:=optional, + com.google.gwt.dev.javac.typemodel;resolution:=optional, + com.google.gwt.dev.shell;resolution:=optional, + com.google.gwt.dev.shell.jetty;resolution:=optional, + com.google.gwt.dev.util.arg;resolution:=optional, + com.google.gwt.dev.util.collect;resolution:=optional, + com.google.gwt.junit;resolution:=optional, + com.google.gwt.thirdparty.guava.common.collect;resolution:=optional, + com.google.gwt.util.tools;resolution:=optional, javax.servlet;version="2.6.0", javax.servlet.http;version="2.6.0", - org.eclipse.jetty.server, - org.eclipse.jetty.webapp + org.eclipse.jetty.server;resolution:=optional, + org.eclipse.jetty.webapp;resolution:=optional diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index 3baf3a3c261..d0122e6a051 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -11,6 +11,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.apache.shiro.SecurityUtils; import org.json.simple.JSONObject; import com.sap.sse.security.User; @@ -29,7 +30,13 @@ public class SecurityResource extends AbstractSecurityResource { @Path("/hello") @Produces("application/json;charset=UTF-8") public Response sayHello() { - return Response.ok("Hello!", MediaType.TEXT_PLAIN).build(); + final String messageText; + if (SecurityUtils.getSubject().isAuthenticated()) { + messageText = "Hello "+SecurityUtils.getSubject().getPrincipal(); + } else { + messageText = "Hello!"; + } + return Response.ok(messageText, MediaType.TEXT_PLAIN).build(); } @POST From 3336007f4eac13a2cb99d288adbe38ad1b5312e5 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 23 Feb 2015 21:04:02 +0100 Subject: [PATCH 179/264] made javax.servlet dependenciees optional in gwt.osgi; added extraRequirement com.google.gwt.dev to target platform for testing --- java/com.google.gwt.osgi/META-INF/MANIFEST.MF | 4 ++-- java/pom.xml | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF index cb85f9ff759..8827322a846 100644 --- a/java/com.google.gwt.osgi/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.osgi/META-INF/MANIFEST.MF @@ -187,7 +187,7 @@ Import-Package: com.gargoylesoftware.htmlunit;resolution:=optional, com.google.gwt.junit;resolution:=optional, com.google.gwt.thirdparty.guava.common.collect;resolution:=optional, com.google.gwt.util.tools;resolution:=optional, - javax.servlet;version="2.6.0", - javax.servlet.http;version="2.6.0", + javax.servlet;version="2.6.0";resolution:=optional, + javax.servlet.http;version="2.6.0";resolution:=optional, org.eclipse.jetty.server;resolution:=optional, org.eclipse.jetty.webapp;resolution:=optional diff --git a/java/pom.xml b/java/pom.xml index 8748125cac6..f027f41b003 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -261,6 +261,15 @@ ${tycho-version} p2 + + + + eclipse-plugin + com.google.gwt.dev + 0.0.0 + + + ${project.groupId} From fb56f225b3fc0867643c98a8115a77e751fc596b Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Tue, 24 Feb 2015 10:30:23 +0100 Subject: [PATCH 180/264] Added interpolation between centers of angle clusters --- .../mining/AngleAndSpeedRegression.java | 2 +- .../sailing/polars/mining/PolarDataMiner.java | 6 ++--- ...eedRegressionPerAngleClusterProcessor.java | 25 +++++++++++++------ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java index 414fcf71e22..ed5736a53dc 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java @@ -60,7 +60,7 @@ public class AngleAndSpeedRegression { } if (angleFound) { result.add(new SpeedWithBearingWithConfidenceImpl(new KnotSpeedWithBearingImpl( - windSpeedCandidateInKnots, new DegreeBearingImpl(angle)), 0.00001, null)); + windSpeedCandidateInKnots, new DegreeBearingImpl(angle)), 0.5 /*FIXME*/, null)); } } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index 5ce57c498c2..d0806be8050 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -183,12 +183,10 @@ public class PolarDataMiner { private Set> getAverageTrueWindSpeedAndAngleCandidatesWithFallbackFunction(BoatClass boatClass, Speed speedOverGround, LegType legType, Tack tack) { - // The following is an estimation function. It only serves as a stub. It's the same for all boatclasses and returns - // default maneuver angles. This is NOT final. + // The following is an estimation function. It only serves as a fallback. It's the same for all boatclasses and returns + // default maneuver angles. // The function is able to return boat speed values for windspeed alues between 5kn and 25kn , which are some kind of realistic // for sailing boats. They are taken from the 505 polars we gathered in the races until now. - // TODO keep a cache of sampling points over which to fit a function for every boat class and then return the - // corresponding angle Set> resultSet = new HashSet<>(); final int tackFactor = (tack.equals(Tack.PORT)) ? -1 : 1; diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java index 722d6ffbd10..d83b7d98e56 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java @@ -9,6 +9,7 @@ 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.polars.regression.IncrementalLeastSquares; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; @@ -48,15 +49,25 @@ private static final Logger logger = Logger.getLogger(CubicRegressionPerCoursePr regression.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); } + /** + * 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. + */ public SpeedWithConfidence estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle) throws NotEnoughDataHasBeenAddedException { - GroupKey key = createGroupKey(boatClass, trueWindAngle); - double estimatedSpeed; - if (regressions.containsKey(key)) { - estimatedSpeed = regressions.get(key).getOrCreatePolynomialFunction().value(windSpeed.getKnots()); - } else { - throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions"); + double speedSum = 0; + double numberOfSpeeds = 0; + for (int i = -5; i <= 5; i++) { + GroupKey key = createGroupKey(boatClass, new DegreeBearingImpl(trueWindAngle.getDegrees() + i)); + if (regressions.containsKey(key)) { + speedSum += regressions.get(key).getOrCreatePolynomialFunction().value(windSpeed.getKnots()); + numberOfSpeeds++; + } else { + throw new NotEnoughDataHasBeenAddedException("Not enough data has been added to Per Course Regressions"); + } } - Speed speed = new KnotSpeedImpl(estimatedSpeed); + Speed speed = new KnotSpeedImpl(speedSum / numberOfSpeeds); return new SpeedWithConfidenceImpl(speed, /*FIXME*/ 0.5, null); } From 24ab13bd8faf5d933e7c9573448d078bb1d40465 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Tue, 24 Feb 2015 11:33:48 +0100 Subject: [PATCH 181/264] Wind, boatSpeed and TWA are only computed once in polar fix --- .../mining/AngleClusterPolarClusterKey.java | 2 +- .../CubicRegressionPerCourseProcessor.java | 2 +- .../mining/GPSFixMovingWithPolarContext.java | 21 +++++++++++++++---- .../mining/MovingAverageProcessorImpl.java | 2 +- .../polars/mining/PolarFixFilterCriteria.java | 2 +- ...eedRegressionPerAngleClusterProcessor.java | 4 ++-- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java index 653db2b0845..2f620b72438 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java @@ -7,6 +7,6 @@ import com.sap.sse.datamining.shared.annotations.Dimension; public interface AngleClusterPolarClusterKey extends BasePolarClusterKey { @Dimension(messageKey = "angleDiffTrueWindToBoat") - Cluster getAngleDiffTrueWindToBoat(); + Cluster getAngleCluster(); } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java index 404a00de67e..5d61079e29f 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java @@ -36,7 +36,7 @@ public class CubicRegressionPerCourseProcessor implements Processor getAverageSpeedAndCourseOverGround(BoatClass boatClass, Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java index 694f4b4fbb7..4646958eb58 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java @@ -35,6 +35,9 @@ public class GPSFixMovingWithPolarContext implements MovingAveragePolarClusterKe private final Set windSourcesToExcludeForBearing; private final Set windSourcesToExcludeForSpeed; private final ClusterGroup angleClusterGroup; + private final BearingWithConfidence trueWindAngle; + private final WindWithConfidence> wind; + private final SpeedWithBearingWithConfidence boatSpeed; public GPSFixMovingWithPolarContext(GPSFixMoving fix, TrackedRace race, Competitor competitor, ClusterGroup windSpeedClusterGroup, ClusterGroup angleClusterGroup) { @@ -45,6 +48,9 @@ public class GPSFixMovingWithPolarContext implements MovingAveragePolarClusterKe this.angleClusterGroup = angleClusterGroup; this.windSourcesToExcludeForBearing = collectWindSourcesToIgnoreForBearing(); this.windSourcesToExcludeForSpeed = collectWindSourcesToIgnoreForSpeed(); + this.trueWindAngle = computeTrueWindAngle(); + this.wind = race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint(), windSourcesToExcludeForSpeed); + this.boatSpeed = computeBoatSpeed(); } public GPSFixMoving getFix() { @@ -60,6 +66,10 @@ public class GPSFixMovingWithPolarContext implements MovingAveragePolarClusterKe } public BearingWithConfidence getAngleToTheWind() { + return trueWindAngle; + } + + private BearingWithConfidence computeTrueWindAngle() { SpeedWithBearing boatSpeed = race.getTrack(competitor).getEstimatedSpeed(fix.getTimePoint()); WindWithConfidence> wind = race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint(), windSourcesToExcludeForBearing); @@ -74,16 +84,19 @@ public class GPSFixMovingWithPolarContext implements MovingAveragePolarClusterKe @Override public Cluster getWindSpeedCluster() { - final WindWithConfidence> windWithConfidence = getWindSpeed(); + final WindWithConfidence> windWithConfidence = getWind(); return windWithConfidence == null ? null : windSpeedClusterGroup.getClusterFor(windWithConfidence.getObject()); } - public WindWithConfidence> getWindSpeed() { - WindWithConfidence> wind = race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint(), windSourcesToExcludeForSpeed); + public WindWithConfidence> getWind() { return wind; } public SpeedWithBearingWithConfidence getBoatSpeed() { + return boatSpeed; + } + + private SpeedWithBearingWithConfidence computeBoatSpeed() { GPSFixTrack track = race.getTrack(competitor); return track.getEstimatedSpeed(fix.getTimePoint(), ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher( // use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero @@ -172,7 +185,7 @@ public class GPSFixMovingWithPolarContext implements MovingAveragePolarClusterKe } @Override - public Cluster getAngleDiffTrueWindToBoat() { + public Cluster getAngleCluster() { return angleClusterGroup.getClusterFor(getAngleToTheWind().getObject()); } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java index e9cfaaae93d..a279e2b57f3 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java @@ -75,7 +75,7 @@ public class MovingAverageProcessorImpl implements MovingAverageProcessor { } GPSFixMovingWithPolarContext fix = element.getDataEntry(); BearingWithConfidence angleToTheWind = fix.getAngleToTheWind(); - WindWithConfidence> windSpeed = fix.getWindSpeed(); + WindWithConfidence> windSpeed = fix.getWind(); SpeedWithBearingWithConfidence boatSpeedWithConfidence = fix.getBoatSpeed(); availableBoatClasses.add(element.getDataEntry().getBoatClass()); WindWithConfidence> windWithConfidenceForSpeed = windSpeed; diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java index c91b91082a6..ecd1c2e610a 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarFixFilterCriteria.java @@ -57,7 +57,7 @@ public class PolarFixFilterCriteria implements FilterCriterion angleToTheWind = element.getAngleToTheWind(); - WindWithConfidence> windSpeed = element.getWindSpeed(); + WindWithConfidence> windSpeed = element.getWind(); SpeedWithBearingWithConfidence boatSpeedWithConfidence = element.getBoatSpeed(); boolean result = false; if (angleToTheWind != null && windSpeed != null && boatSpeedWithConfidence != null) { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java index d83b7d98e56..ce309df905e 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/SpeedRegressionPerAngleClusterProcessor.java @@ -46,7 +46,7 @@ private static final Logger logger = Logger.getLogger(CubicRegressionPerCoursePr } } GPSFixMovingWithPolarContext fix = element.getDataEntry(); - regression.addData(fix.getWindSpeed().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); + regression.addData(fix.getWind().getObject().getKnots(), fix.getBoatSpeed().getObject().getKnots()); } /** @@ -80,7 +80,7 @@ private static final Logger logger = Logger.getLogger(CubicRegressionPerCoursePr } @Override - public Cluster getAngleDiffTrueWindToBoat() { + public Cluster getAngleCluster() { return angleClusterGroup.getClusterFor(angle); } From 48e0cf11a77096030c3672d4d8d08c33cedec293 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Tue, 24 Feb 2015 12:59:27 +0100 Subject: [PATCH 182/264] Introduced max validity range for polar regressions --- .../mining/AngleAndSpeedRegression.java | 10 +++++- .../mining/AngleClusterPolarClusterKey.java | 2 +- .../mining/MovingAverageProcessorImpl.java | 31 ------------------- .../PolarDataDimensionCollectionFactory.java | 6 ++-- 4 files changed, 14 insertions(+), 35 deletions(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java index ed5736a53dc..f635e120014 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java @@ -24,16 +24,24 @@ public class AngleAndSpeedRegression { private final IncrementalLeastSquares speedRegression = new IncrementalAnyOrderLeastSquaresImpl(3, false); private final IncrementalLeastSquares angleRegression = new IncrementalAnyOrderLeastSquaresImpl(3); + + private double maxWindSpeedInKnots = -1; public void addData(WindWithConfidence> windSpeed, BearingWithConfidence angleToTheWind, SpeedWithBearingWithConfidence boatSpeed) { double windSpeedInKnots = windSpeed.getObject().getKnots(); + if (windSpeedInKnots > maxWindSpeedInKnots) { + maxWindSpeedInKnots = windSpeedInKnots; + } speedRegression.addData(windSpeedInKnots, boatSpeed.getObject().getKnots()); angleRegression.addData(windSpeedInKnots, angleToTheWind.getObject().getDegrees()); } public SpeedWithBearingWithConfidence estimateSpeedAndAngle(Speed windSpeed) throws NotEnoughDataHasBeenAddedException { double windSpeedInKnots = windSpeed.getKnots(); + if (windSpeedInKnots > maxWindSpeedInKnots) { + throw new NotEnoughDataHasBeenAddedException(); + } double estimatedSpeed = speedRegression.getOrCreatePolynomialFunction().value(windSpeedInKnots); double estimatedAngle = angleRegression.getOrCreatePolynomialFunction().value(windSpeedInKnots); Bearing bearing = new DegreeBearingImpl(estimatedAngle); @@ -49,7 +57,7 @@ public class AngleAndSpeedRegression { Set> result = new HashSet<>(); for (int i = 0; i < windSpeedCandidates.length; i++) { double windSpeedCandidateInKnots = windSpeedCandidates[i]; - if (windSpeedCandidateInKnots >= 0) { + if (windSpeedCandidateInKnots >= 0 && windSpeedCandidateInKnots <= maxWindSpeedInKnots) { double angle = 0; boolean angleFound; try { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java index 2f620b72438..48826f625bc 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleClusterPolarClusterKey.java @@ -6,7 +6,7 @@ import com.sap.sse.datamining.shared.annotations.Dimension; public interface AngleClusterPolarClusterKey extends BasePolarClusterKey { - @Dimension(messageKey = "angleDiffTrueWindToBoat") + @Dimension(messageKey = "angleCluster") Cluster getAngleCluster(); } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java index a279e2b57f3..a7cf39d026b 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/MovingAverageProcessorImpl.java @@ -1,9 +1,7 @@ package com.sap.sailing.polars.mining; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; @@ -21,12 +19,10 @@ import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.confidence.BearingWithConfidence; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.polars.regression.MovingAverageBoatSpeedEstimator; import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException; -import com.sap.sailing.polars.regression.impl.WindSpeedAndAngleEstimator; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; import com.sap.sse.datamining.AdditionalResultDataBuilder; @@ -137,33 +133,6 @@ public class MovingAverageProcessorImpl implements MovingAverageProcessor { } return new DegreeBearingImpl(averageAngleContainer.getAverageAngleDeg()); } - - Set> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass, - Speed speedOverGround, LegType legType, Tack tack) { - List>> averageBoatSpeedAndCourseForWindSpeed = obtainPolarSamplingPoints( - boatClass, legType, tack); - - WindSpeedAndAngleEstimator windSpeedAndAngleEstimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed); - return windSpeedAndAngleEstimator.getAverageTrueWindSpeedAndAngleCandidates(speedOverGround); - } - - private List>> obtainPolarSamplingPoints(BoatClass boatClass, - LegType legType, Tack tack) { - List>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>(); - for (int i = 0; i < 60; i++) { - Speed windSpeed = new KnotSpeedImpl(i * 0.5); - SpeedWithBearingWithConfidence averageSpeedAndCourseOverGround; - try { - averageSpeedAndCourseOverGround = getAverageSpeedAndCourseOverGround( - boatClass, windSpeed, legType, tack); - averageBoatSpeedAndCourseForWindSpeed.add(new Pair>(windSpeed, - averageSpeedAndCourseOverGround)); - } catch (NotEnoughDataHasBeenAddedException e) { - // ignore this, since there is not enough data for this windspeed. - } - } - return averageBoatSpeedAndCourseForWindSpeed; - } @Override public void onFailure(Throwable failure) { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java index 656b7f985b0..19f4ef9b83e 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataDimensionCollectionFactory.java @@ -4,8 +4,10 @@ import java.util.ArrayList; import java.util.Collection; import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.Tack; +import com.sap.sse.datamining.data.Cluster; import com.sap.sse.datamining.factories.FunctionFactory; import com.sap.sse.datamining.functions.Function; @@ -60,8 +62,8 @@ public class PolarDataDimensionCollectionFactory { addPolarBaseDimension(dimensions, functionFactory); - Function angleDiffTrueWindToBoatFunction = functionFactory.createMethodWrappingFunction(AngleClusterPolarClusterKey.class - .getMethod("getAngleDiffTrueWindToBoat", new Class[0])); + Function> angleDiffTrueWindToBoatFunction = functionFactory.createMethodWrappingFunction(AngleClusterPolarClusterKey.class + .getMethod("getAngleCluster", new Class[0])); dimensions.add(angleDiffTrueWindToBoatFunction); return dimensions; From 695ebe0be9f02483e294a3c4776cfd9359b22329 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Feb 2015 15:34:58 +0100 Subject: [PATCH 183/264] fixed media types for SecurityResource --- .../security/jaxrs/api/SecurityResource.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index d0122e6a051..ad830da8e68 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -23,12 +23,12 @@ public class SecurityResource extends AbstractSecurityResource { private static final Logger logger = Logger.getLogger(SecurityResource.class.getName()); private Response getSecurityErrorResponse(String msg) { - return Response.status(Status.BAD_REQUEST).entity(msg).type(MediaType.TEXT_PLAIN).build(); + return Response.status(Status.BAD_REQUEST).entity(msg).type(MediaType.TEXT_PLAIN_TYPE).build(); } @GET @Path("/hello") - @Produces("application/json;charset=UTF-8") + @Produces("text/plain;charset=UTF-8") public Response sayHello() { final String messageText; if (SecurityUtils.getSubject().isAuthenticated()) { @@ -36,12 +36,12 @@ public class SecurityResource extends AbstractSecurityResource { } else { messageText = "Hello!"; } - return Response.ok(messageText, MediaType.TEXT_PLAIN).build(); + return Response.ok(messageText, MediaType.TEXT_PLAIN_TYPE).build(); } @POST @Path("/login") - @Produces("application/json;charset=UTF-8") + @Produces("text/plain;charset=UTF-8") public Response login(@FormParam("username") String username, @FormParam("password") String password) { try { getService().login(username, password); @@ -50,7 +50,7 @@ public class SecurityResource extends AbstractSecurityResource { logger.info("Logging in " + username + " with password failed: "+e.getMessage()); return getSecurityErrorResponse(e.getMessage()); } - return Response.ok("Logged in!", MediaType.TEXT_PLAIN).build(); + return Response.ok("Logged in!", MediaType.TEXT_PLAIN_TYPE).build(); } @POST @@ -63,7 +63,7 @@ public class SecurityResource extends AbstractSecurityResource { JSONObject response = new JSONObject(); response.put("username", username); response.put("access_token", getService().createAccessToken(username)); - return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON).build(); + return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); } catch (UserManagementException e) { logger.info("Logging in " + username + " with password failed: "+e.getMessage()); return getSecurityErrorResponse(e.getMessage()); @@ -85,14 +85,15 @@ public class SecurityResource extends AbstractSecurityResource { JSONObject response = new JSONObject(); response.put("username", user.getName()); response.put("email", user.getEmail()); - return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON).build(); + return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); } @GET @Path("/logout") + @Produces("text/plain;charset=UTF-8") public Response logout() { getService().logout(); - return Response.ok("Logged out", MediaType.TEXT_PLAIN).build(); + return Response.ok("Logged out", MediaType.TEXT_PLAIN_TYPE).build(); } } \ No newline at end of file From 1c25d8a6523e2438315fd8a3e29bed2c1e041657 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Tue, 24 Feb 2015 16:55:59 +0100 Subject: [PATCH 184/264] Use new regression based polars where possible --- .../sailing/server/gateway/jaxrs/api/PolarResource.java | 2 +- .../com/sap/sailing/simulator/impl/PolarDiagramGPS.java | 4 ++-- .../sap/sailing/simulator/impl/PolarDiagramGPSDirect.java | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java index 4017075d50b..f058263a9a2 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java @@ -85,7 +85,7 @@ public class PolarResource extends AbstractSailingServerResource { try { PolarDataService service = getService().getPolarDataService(); SpeedWithBearingWithConfidence speedWithBearing = service.getAverageSpeedWithBearing(boatClass, - windSpeed, legType, tack, false); + windSpeed, legType, tack, true); String resultString = "Speed: " + speedWithBearing.getObject().getKnots() + "kn; Angle: " + speedWithBearing.getObject().getBearing().getDegrees() + "°; Confidence: " + speedWithBearing.getConfidence(); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPS.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPS.java index 3bd4b43d036..371bbee939e 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPS.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPS.java @@ -51,8 +51,8 @@ public class PolarDiagramGPS extends PolarDiagramBase { SpeedWithBearing beatStar; for (int i = 0; i < windSpeeds.size(); i++) { try { - beatPort = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.UPWIND, Tack.PORT, false).getObject(); - beatStar = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.UPWIND, Tack.STARBOARD, false).getObject(); + beatPort = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.UPWIND, Tack.PORT, true).getObject(); + beatStar = this.polarData.getAverageSpeedWithBearing(this.boatClass, windSpeeds.get(i), LegType.UPWIND, Tack.STARBOARD, true).getObject(); } catch (NotEnoughDataHasBeenAddedException e) { beatPort = null; beatStar = null; diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPSDirect.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPSDirect.java index 35730c1d0bb..37ee5c33e23 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPSDirect.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/PolarDiagramGPSDirect.java @@ -131,11 +131,11 @@ public class PolarDiagramGPSDirect implements PolarDiagram, Serializable { public Bearing[] optimalDirectionsUpwind() { try { if (beatPort == null) { - SpeedWithBearing beatPortRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.UPWIND, Tack.PORT, false).getObject(); + SpeedWithBearing beatPortRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.UPWIND, Tack.PORT, true).getObject(); beatPort = new KnotSpeedWithBearingImpl(beatPortRelative.getKnots(), this.wind.getBearing().reverse().add(beatPortRelative.getBearing())); } if (beatStar == null) { - SpeedWithBearing beatStarRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.UPWIND, Tack.STARBOARD, false).getObject(); + SpeedWithBearing beatStarRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.UPWIND, Tack.STARBOARD, true).getObject(); beatStar = new KnotSpeedWithBearingImpl(beatStarRelative.getKnots(), this.wind.getBearing().reverse().add(beatStarRelative.getBearing())); } } catch (NotEnoughDataHasBeenAddedException e) { @@ -156,11 +156,11 @@ public class PolarDiagramGPSDirect implements PolarDiagram, Serializable { public Bearing[] optimalDirectionsDownwind() { try { if (jibePort ==null) { - SpeedWithBearing jibePortRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.DOWNWIND, Tack.PORT, false).getObject(); + SpeedWithBearing jibePortRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.DOWNWIND, Tack.PORT, true).getObject(); jibePort = new KnotSpeedWithBearingImpl(jibePortRelative.getKnots(), this.wind.getBearing().reverse().add(jibePortRelative.getBearing())); } if (jibeStar == null) { - SpeedWithBearing jibeStarRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.DOWNWIND, Tack.STARBOARD, false).getObject(); + SpeedWithBearing jibeStarRelative = this.polarData.getAverageSpeedWithBearing(this.boatClass, wind, LegType.DOWNWIND, Tack.STARBOARD, true).getObject(); jibeStar = new KnotSpeedWithBearingImpl(jibeStarRelative.getKnots(), this.wind.getBearing().reverse().add(jibeStarRelative.getBearing())); } } catch (NotEnoughDataHasBeenAddedException e) { From cf010e8c029107a1272d5fdb008e7ed5b5dc7b3c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 24 Feb 2015 21:58:02 +0100 Subject: [PATCH 185/264] provide and use a BearerTokenAuthenticationFilter which defaults to HTTP basic authentication; renamed AccessTokenRealm to BearerTokenRealm --- .../src/main/resources/shiro.ini | 4 +- .../src/main/resources/shiro.ini | 4 +- java/com.sap.sse.security/resources/shiro.ini | 14 ++++--- ...en.java => BearerAuthenticationToken.java} | 12 +++--- .../BearerTokenAuthenticationFilter.java | 41 +++++++++++++++++++ ...sTokenRealm.java => BearerTokenRealm.java} | 12 ++---- .../src/com/sap/sse/security/OAuthRealm.java | 1 + .../sse/security/UsernamePasswordRealm.java | 1 + .../security/impl/SecurityServiceImpl.java | 4 +- 9 files changed, 67 insertions(+), 26 deletions(-) rename java/com.sap.sse.security/src/com/sap/sse/security/{AccessToken.java => BearerAuthenticationToken.java} (70%) create mode 100755 java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java rename java/com.sap.sse.security/src/com/sap/sse/security/{AccessTokenRealm.java => BearerTokenRealm.java} (77%) diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini index d1980ba70b0..83eb09f8bdc 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini @@ -18,10 +18,10 @@ upRealm.credentialsMatcher = $credentialsMatcher upRealm.permissionsForRoleProvider = $permissionsForRoleProvider oauthRealm = com.sap.sse.security.OAuthRealm oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider -accessTokenRealm = com.sap.sse.security.AccessTokenRealm +bearerTokenRealm = com.sap.sse.security.BearerTokenRealm accessTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider -securityManager.realms = $upRealm, $oauthRealm, $accessTokenRealm +securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager diff --git a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini index 06ec02bbf2f..df6c11f0518 100755 --- a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini @@ -7,9 +7,9 @@ credentialsMatcher.hashIterations = 1024 upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher oauthRealm = com.sap.sse.security.OAuthRealm -accessTokenRealm = com.sap.sse.security.AccessTokenRealm +bearerTokenRealm = com.sap.sse.security.BearerTokenRealm -securityManager.realms = $upRealm, $oauthRealm, $accessTokenRealm +securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index 0be28cb3e39..a540787dd95 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -10,16 +10,18 @@ credentialsMatcher.hashIterations = 1024 upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher oauthRealm = com.sap.sse.security.OAuthRealm -accessTokenRealm = com.sap.sse.security.AccessTokenRealm +bearerTokenRealm = com.sap.sse.security.BearerTokenRealm -securityManager.realms = $upRealm, $oauthRealm, $accessTokenRealm +securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager -authc = com.sap.sse.security.CustomFilter -authc.loginUrl = /security/ui/Login.html -authc.successUrl = /UserManagement.html +customGwt = com.sap.sse.security.CustomFilter +customGwt.loginUrl = /security/ui/Login.html +customGwt.successUrl = /UserManagement.html + +bearerToken = com.sap.sse.security.BearerTokenAuthenticationFilter sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO securityManager.sessionManager.sessionDAO = $sessionDAO @@ -30,4 +32,4 @@ securityManager.cacheManager = $cacheManager [urls] /api/restsecurity/login = anon /api/restsecurity/logout = logout -/api/restsecurity/hello = authc \ No newline at end of file +/api/restsecurity/hello = bearerToken diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java b/java/com.sap.sse.security/src/com/sap/sse/security/BearerAuthenticationToken.java similarity index 70% rename from java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java rename to java/com.sap.sse.security/src/com/sap/sse/security/BearerAuthenticationToken.java index bacf4debc85..6d839cbd7df 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/AccessToken.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/BearerAuthenticationToken.java @@ -11,25 +11,25 @@ import com.sap.sse.security.impl.Activator; * @author Axel Uhl (D043530) * */ -public class AccessToken implements AuthenticationToken { +public class BearerAuthenticationToken implements AuthenticationToken { private static final long serialVersionUID = 8528031991813216585L; - private final String accessToken; + private final String token; - public AccessToken(String accessToken) { + public BearerAuthenticationToken(String token) { super(); - this.accessToken = accessToken; + this.token = token; } @Override public Object getPrincipal() { SecurityService securityService = Activator.getSecurityService(); - User user = securityService.getUserByAccessToken(accessToken); + User user = securityService.getUserByAccessToken(token); return user == null ? null : user.getName(); } @Override public String getCredentials() { - return accessToken; + return token; } } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java new file mode 100755 index 00000000000..4d1f85ee967 --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java @@ -0,0 +1,41 @@ +package com.sap.sse.security; + + +import java.util.Locale; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; + +/** + * Looks for an "Authorization: Bearer <token>" HTTP header. If found, tries to authenticate a user + * with the bearer token presented. If instead an "Authorization: Basic ..." header field is found, HTTP + * basic authentication is attempted using a regular username/password token. + * + * @author Axel Uhl (D043530) + * + */ +public class BearerTokenAuthenticationFilter extends BasicHttpAuthenticationFilter { + + private static final String BEARER = "Bearer"; + + @Override + protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { + String authorizationHeader = getAuthzHeader(request); + if (authorizationHeader != null && !authorizationHeader.isEmpty()) { + String[] authTokens = authorizationHeader.split(" "); + if (authTokens[0].equals(BEARER)) { + return new BearerAuthenticationToken(authTokens[1]); + } + } + return super.createToken(request, response); + } + + @Override + protected boolean isLoginAttempt(String authzHeader) { + String authzScheme = BEARER.toLowerCase(Locale.ENGLISH); + return authzHeader.toLowerCase(Locale.ENGLISH).startsWith(authzScheme) || super.isLoginAttempt(authzHeader); + } +} diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AccessTokenRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenRealm.java similarity index 77% rename from java/com.sap.sse.security/src/com/sap/sse/security/AccessTokenRealm.java rename to java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenRealm.java index c920f0f7b80..ed4d2fb22c0 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/AccessTokenRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenRealm.java @@ -12,20 +12,16 @@ import org.apache.shiro.authc.SaltedAuthenticationInfo; * @author Axel Uhl (D043530) * */ -public class AccessTokenRealm extends AbstractUserStoreBasedRealm { +public class BearerTokenRealm extends AbstractUserStoreBasedRealm { - public AccessTokenRealm() { + public BearerTokenRealm() { super(); + setAuthenticationTokenClass(BearerAuthenticationToken.class); } - @Override - public boolean supports(AuthenticationToken token) { - return token instanceof AccessToken; - } - @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { - AccessToken accessToken = (AccessToken) token; + BearerAuthenticationToken accessToken = (BearerAuthenticationToken) token; final User user = getUserStore().getUserByAccessToken(accessToken.getCredentials()); if (user == null) { return null; diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java index ea139f5f7be..9326ddf7ac3 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java @@ -42,6 +42,7 @@ public class OAuthRealm extends AbstractUserStoreBasedRealm { public OAuthRealm() { super(); + setAuthenticationTokenClass(OAuthToken.class); } @Override diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java index b473a70c388..c265e6c7ef1 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/UsernamePasswordRealm.java @@ -13,6 +13,7 @@ public class UsernamePasswordRealm extends AbstractUserStoreBasedRealm { public UsernamePasswordRealm() { super(); + setAuthenticationTokenClass(UsernamePasswordToken.class); } @Override diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index 778ef3efb92..a82d3fac632 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -70,7 +70,7 @@ 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.security.AccessToken; +import com.sap.sse.security.BearerAuthenticationToken; import com.sap.sse.security.ClientUtils; import com.sap.sse.security.Credential; import com.sap.sse.security.GithubApi; @@ -291,7 +291,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica @Override public User loginByAccessToken(String accessToken) { - AccessToken token = new AccessToken(accessToken); + BearerAuthenticationToken token = new BearerAuthenticationToken(accessToken); logger.info("Trying to login with access token"); Subject subject = SecurityUtils.getSubject(); try { From 6076f03c601cea050e1dca0eca6986d678c1a970 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 00:53:43 +0100 Subject: [PATCH 186/264] enhanced logon procedures and fixed test cases --- .../src/main/resources/shiro.ini | 2 +- .../api}/SecurityResourceTest.java | 16 ++-- java/com.sap.sse.security/resources/shiro.ini | 6 +- .../BearerTokenAuthenticationFilter.java | 34 ++++++-- .../security/jaxrs/api/SecurityResource.java | 85 ++++++------------- 5 files changed, 70 insertions(+), 73 deletions(-) rename java/com.sap.sse.security.test/src/com/sap/sse/security/{test => jaxrs/api}/SecurityResourceTest.java (86%) diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini index 83eb09f8bdc..d4dfdff48d2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini @@ -19,7 +19,7 @@ upRealm.permissionsForRoleProvider = $permissionsForRoleProvider oauthRealm = com.sap.sse.security.OAuthRealm oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider bearerTokenRealm = com.sap.sse.security.BearerTokenRealm -accessTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider +bearerTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java similarity index 86% rename from java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java rename to java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java index 1f017b671d2..b1ddc5ff77e 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/test/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java @@ -1,12 +1,10 @@ -package com.sap.sse.security.test; +package com.sap.sse.security.jaxrs.api; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import javax.ws.rs.core.Response; - import org.apache.shiro.SecurityUtils; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; @@ -17,11 +15,12 @@ import org.junit.Before; import org.junit.Test; import com.sap.sse.common.mail.MailException; +import com.sap.sse.security.BearerAuthenticationToken; import com.sap.sse.security.SecurityService; +import com.sap.sse.security.User; import com.sap.sse.security.UsernamePasswordRealm; import com.sap.sse.security.impl.Activator; import com.sap.sse.security.impl.SecurityServiceImpl; -import com.sap.sse.security.jaxrs.api.SecurityResource; import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.userstore.mongodb.PersistenceFactory; import com.sap.sse.security.userstore.mongodb.UserStoreImpl; @@ -59,7 +58,7 @@ public class SecurityResourceTest { } private String createAccessToken() throws ParseException { - String responseJsonString = (String) servlet.accessToken("admin", "admin").getEntity(); + String responseJsonString = (String) servlet.respondWithAccessTokenForUser("admin").getEntity(); JSONObject responseJson = (JSONObject) new JSONParser().parse(responseJsonString); assertEquals("admin", responseJson.get("username")); String accessToken = (String) responseJson.get("access_token"); @@ -70,10 +69,11 @@ public class SecurityResourceTest { @Test public void createAccessTokenAndAuthenticate() throws ParseException { String accessToken = createAccessToken(); - Response response = servlet.authenticate(accessToken); - JSONObject responseJson = (JSONObject) new JSONParser().parse((String) response.getEntity()); - assertEquals("admin", responseJson.get("username")); + User user = service.getUserByAccessToken(accessToken); + assertNotNull(user); + assertEquals("admin", user.getName()); final Subject subject = SecurityUtils.getSubject(); + subject.login(new BearerAuthenticationToken(accessToken)); assertTrue(subject.isAuthenticated()); assertEquals("admin", subject.getPrincipal()); assertTrue(subject.isPermitted("can do")); diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index a540787dd95..43a161f34a9 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -21,6 +21,8 @@ customGwt = com.sap.sse.security.CustomFilter customGwt.loginUrl = /security/ui/Login.html customGwt.successUrl = /UserManagement.html +authc.loginUrl=/api/restsecurity/login + bearerToken = com.sap.sse.security.BearerTokenAuthenticationFilter sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO @@ -30,6 +32,8 @@ cacheManager = com.sap.sse.security.SessionCacheManager securityManager.cacheManager = $cacheManager [urls] -/api/restsecurity/login = anon +/api/restsecurity/login = authc /api/restsecurity/logout = logout +/api/restsecurity/access_token = bearerToken /api/restsecurity/hello = bearerToken +/api/restsecurity/hello_post = bearerToken diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java index 4d1f85ee967..ef0340a180f 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java @@ -5,14 +5,24 @@ import java.util.Locale; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; +import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; + +import com.sap.sse.security.impl.FormAuthenticationFilterWithPublicCreateToken; +import com.sap.sse.security.jaxrs.api.SecurityResource; /** * Looks for an "Authorization: Bearer <token>" HTTP header. If found, tries to authenticate a user * with the bearer token presented. If instead an "Authorization: Basic ..." header field is found, HTTP - * basic authentication is attempted using a regular username/password token. + * basic authentication is attempted using a regular username/password token. If that isn't found, looks + * for username/password fields in the POST parameters, like the {@link FormAuthenticationFilter} does.

      + * + * This filter can be used for securing REST requests, in conjunction with the {@link SecurityResource}'s + * {@link SecurityResource#accessToken(String, String)} through which a bearer token can be obtained using + * basic authentication. * * @author Axel Uhl (D043530) * @@ -20,22 +30,36 @@ import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; public class BearerTokenAuthenticationFilter extends BasicHttpAuthenticationFilter { private static final String BEARER = "Bearer"; + + /** + * Used to delegate requests to in case no bearer token and no basic authentication is found + * in the "Authorization:" header field. + */ + private final FormAuthenticationFilterWithPublicCreateToken formAuthenticationFilter; + + public BearerTokenAuthenticationFilter() { + formAuthenticationFilter = new FormAuthenticationFilterWithPublicCreateToken(); + } @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { String authorizationHeader = getAuthzHeader(request); if (authorizationHeader != null && !authorizationHeader.isEmpty()) { String[] authTokens = authorizationHeader.split(" "); - if (authTokens[0].equals(BEARER)) { + if (authTokens[0].equalsIgnoreCase(BEARER)) { return new BearerAuthenticationToken(authTokens[1]); + } else if (authTokens[0].equalsIgnoreCase(HttpServletRequest.BASIC_AUTH)) { + return super.createToken(request, response); } } - return super.createToken(request, response); + return formAuthenticationFilter.createToken(request, response); } @Override - protected boolean isLoginAttempt(String authzHeader) { + protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { String authzScheme = BEARER.toLowerCase(Locale.ENGLISH); - return authzHeader.toLowerCase(Locale.ENGLISH).startsWith(authzScheme) || super.isLoginAttempt(authzHeader); + final String authzHeader = getAuthzHeader(request); + return (authzHeader != null && authzHeader.toLowerCase(Locale.ENGLISH).startsWith(authzScheme)) || + super.isLoginAttempt(request, response) || formAuthenticationFilter.isLoginSubmission(request, response); } } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index ad830da8e68..ece7d27cbd2 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -1,31 +1,19 @@ package com.sap.sse.security.jaxrs.api; -import java.util.logging.Logger; - -import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; import org.apache.shiro.SecurityUtils; import org.json.simple.JSONObject; -import com.sap.sse.security.User; import com.sap.sse.security.jaxrs.AbstractSecurityResource; -import com.sap.sse.security.shared.UserManagementException; @Path("/restsecurity") public class SecurityResource extends AbstractSecurityResource { - private static final Logger logger = Logger.getLogger(SecurityResource.class.getName()); - - private Response getSecurityErrorResponse(String msg) { - return Response.status(Status.BAD_REQUEST).entity(msg).type(MediaType.TEXT_PLAIN_TYPE).build(); - } - @GET @Path("/hello") @Produces("text/plain;charset=UTF-8") @@ -38,62 +26,43 @@ public class SecurityResource extends AbstractSecurityResource { } return Response.ok(messageText, MediaType.TEXT_PLAIN_TYPE).build(); } - + @POST - @Path("/login") + @Path("/hello") @Produces("text/plain;charset=UTF-8") - public Response login(@FormParam("username") String username, @FormParam("password") String password) { - try { - getService().login(username, password); - logger.info("Successfully logged in " + username + " with password"); - } catch (UserManagementException e) { - logger.info("Logging in " + username + " with password failed: "+e.getMessage()); - return getSecurityErrorResponse(e.getMessage()); + public Response sayHelloPost() { + final String messageText; + if (SecurityUtils.getSubject().isAuthenticated()) { + messageText = "Hello "+SecurityUtils.getSubject().getPrincipal(); + } else { + messageText = "Hello!"; } - return Response.ok("Logged in!", MediaType.TEXT_PLAIN_TYPE).build(); + return Response.ok(messageText, MediaType.TEXT_PLAIN_TYPE).build(); + } + + @GET + @Path("/access_token") + @Produces("application/json;charset=UTF-8") + public Response accessToken() { + return respondWithAccessTokenForAuthenticatedSubject(); } @POST @Path("/access_token") @Produces("application/json;charset=UTF-8") - public Response accessToken(@FormParam("username") String username, @FormParam("password") String password) { - try { - getService().login(username, password); - logger.info("Successfully logged in " + username + " with password"); - JSONObject response = new JSONObject(); - response.put("username", username); - response.put("access_token", getService().createAccessToken(username)); - return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); - } catch (UserManagementException e) { - logger.info("Logging in " + username + " with password failed: "+e.getMessage()); - return getSecurityErrorResponse(e.getMessage()); - } + public Response accessTokenPost() { + return respondWithAccessTokenForAuthenticatedSubject(); } - @POST - @Path("/authenticate") - @Produces("application/json;charset=UTF-8") - public Response authenticate(@FormParam("access_token") String accessToken) { - User user = getService().loginByAccessToken(accessToken); - if (user == null) { - logger.info("Invalid access token " + accessToken); - return Response.status(Status.UNAUTHORIZED).entity("Access token " + accessToken + " not recognized") - .build(); - } else { - logger.info("Authenticated " + user.getName() + " with access token"); - } + private Response respondWithAccessTokenForAuthenticatedSubject() { + final String username = SecurityUtils.getSubject().getPrincipal().toString(); + return respondWithAccessTokenForUser(username); + } + + Response respondWithAccessTokenForUser(final String username) { JSONObject response = new JSONObject(); - response.put("username", user.getName()); - response.put("email", user.getEmail()); + response.put("username", username); + response.put("access_token", getService().createAccessToken(username)); return Response.ok(response.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); } - - @GET - @Path("/logout") - @Produces("text/plain;charset=UTF-8") - public Response logout() { - getService().logout(); - return Response.ok("Logged out", MediaType.TEXT_PLAIN_TYPE).build(); - } -} - \ No newline at end of file +} \ No newline at end of file From 3bc7818939769dd8578542533219e5d168d7c981 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 25 Feb 2015 08:35:31 +0100 Subject: [PATCH 187/264] Added web service for showing polar regression functions This should simplify the extraction of "default" polars for each boat class. --- .../sap/sailing/polars/PolarDataService.java | 28 +++++++++++++++ .../polars/impl/PolarDataServiceImpl.java | 14 ++++++++ .../mining/AngleAndSpeedRegression.java | 10 ++++++ .../CubicRegressionPerCourseProcessor.java | 26 ++++++++++++++ .../sailing/polars/mining/PolarDataMiner.java | 12 +++++++ .../META-INF/MANIFEST.MF | 3 +- .../gateway/jaxrs/api/PolarResource.java | 36 +++++++++++++++++-- 7 files changed, 125 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java index 3b6bfd1596c..63209c8a42e 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java @@ -4,6 +4,8 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; @@ -165,4 +167,30 @@ public interface PolarDataService { * speed and true wind angle in the {@link Pair#getB() second} component. */ Pair> getManeuverLikelihoodAndTwsTwa(BoatClass boatClass, Speed speedOverGround, double courseChangeDeg, ManeuverType maneuverType); + + /** + * This method is not intended to be used directly apart from debugging purposes. If you intend to use the polar service please + * use the {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack, boolean)} method. + * + * @param boatClass + * @param legType + * @param tack + * @return The estimating function for the tack and legtype combination estimating boatspeed over windspeed for the + * given boat class. All values in kn. + * @throws NotEnoughDataHasBeenAddedException + */ + PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; + + /** + * This method is not intended to be used directly apart from debugging purposes. If you intend to use the polar service please + * use the {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack, boolean)} method. + * + * @param boatClass + * @param legType + * @param tack + * @return The estimating function for the tack and legtype combination estimating true wind angle over windspeed for the + * given boat class. TWA in degrees and windspeeds in knots. + * @throws NotEnoughDataHasBeenAddedException + */ + PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException; } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java index d5ccfa7f6c5..01352002829 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java @@ -4,6 +4,8 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; @@ -132,4 +134,16 @@ public class PolarDataServiceImpl implements PolarDataService { } return closestTwsTwa; } + + @Override + public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) + throws NotEnoughDataHasBeenAddedException { + return polarDataMiner.getSpeedRegressionFunction(boatClass, legType, tack); + } + + @Override + public PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType, Tack tack) + throws NotEnoughDataHasBeenAddedException { + return polarDataMiner.getAngleRegressionFunction(boatClass, legType, tack); + } } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java index f635e120014..d894bb15da1 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java @@ -3,6 +3,8 @@ package com.sap.sailing.polars.mining; import java.util.HashSet; import java.util.Set; +import org.apache.commons.math.analysis.polynomials.PolynomialFunction; + import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl; import com.sap.sailing.domain.common.Bearing; @@ -76,4 +78,12 @@ public class AngleAndSpeedRegression { return result; } + public PolynomialFunction getSpeedRegressionFunction() throws NotEnoughDataHasBeenAddedException { + return speedRegression.getOrCreatePolynomialFunction(); + } + + public PolynomialFunction getAngleRegressionFunction() throws NotEnoughDataHasBeenAddedException { + return angleRegression.getOrCreatePolynomialFunction(); + } + } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java index 5d61079e29f..2b789fc8df2 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/CubicRegressionPerCourseProcessor.java @@ -6,6 +6,8 @@ import java.util.Map; import java.util.Set; 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; @@ -95,6 +97,30 @@ public class CubicRegressionPerCourseProcessor implements Processor boatClasses = polarDataService.getAllBoatClassesWithPolarSheetsAvailable(); + ResponseBuilder responseBuilder; + StringBuilder stringBuilder = new StringBuilder(); + for (BoatClass boatClass : boatClasses) { + for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) { + for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) { + try { + PolynomialFunction speedFunction = polarDataService.getSpeedRegressionFunction(boatClass, legType, tack); + stringBuilder.append("Speed: " + boatClass + " " + legType + " " + tack + ": " + speedFunction.toString() + "\n"); + PolynomialFunction angleFunction = polarDataService.getAngleRegressionFunction(boatClass, legType, tack); + stringBuilder.append("Angle: " + boatClass + " " + legType + " " + tack + ": " + angleFunction.toString() + "\n"); + } catch (NotEnoughDataHasBeenAddedException e) { + stringBuilder.append("No data for " + boatClass + " " + legType + " " + tack + "\n"); + } + } + } + stringBuilder.append("\n\n"); + } + + responseBuilder = Response.ok(stringBuilder.toString(), MediaType.TEXT_PLAIN); + return responseBuilder.build(); + } + @GET @Produces("text/plain;charset=UTF-8") @Path("average/{boatClassName}") From 8d76c78c48337e1e06eb4bf7cf1db1121f68682a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 08:38:53 +0100 Subject: [PATCH 188/264] added missing class FormAuthenticationFilterWithPublicCreateToken which is a delegate for the bearer token authentication filter --- ...enticationFilterWithPublicCreateToken.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 java/com.sap.sse.security/src/com/sap/sse/security/impl/FormAuthenticationFilterWithPublicCreateToken.java diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/FormAuthenticationFilterWithPublicCreateToken.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/FormAuthenticationFilterWithPublicCreateToken.java new file mode 100755 index 00000000000..2ab2706e1fe --- /dev/null +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/FormAuthenticationFilterWithPublicCreateToken.java @@ -0,0 +1,20 @@ +package com.sap.sse.security.impl; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; + +public class FormAuthenticationFilterWithPublicCreateToken extends FormAuthenticationFilter { + + @Override + public AuthenticationToken createToken(ServletRequest request, ServletResponse response) { + return super.createToken(request, response); + } + + @Override + public boolean isLoginSubmission(ServletRequest request, ServletResponse response) { + return super.isLoginSubmission(request, response); + } +} From 92eb4a9374d53aee5f81476258fff693cae90d91 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Wed, 25 Feb 2015 09:31:21 +0100 Subject: [PATCH 189/264] Fixed error in bearing cluster group construction also added test case --- .../test/BearingClusterGroupTest.java | 29 +++++++++++++++++++ .../polars/mining/BearingClusterGroup.java | 5 ++-- 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/clusters/test/BearingClusterGroupTest.java diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/clusters/test/BearingClusterGroupTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/clusters/test/BearingClusterGroupTest.java new file mode 100644 index 00000000000..25108f84544 --- /dev/null +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/clusters/test/BearingClusterGroupTest.java @@ -0,0 +1,29 @@ +package com.sap.sailing.polars.clusters.test; + +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.polars.mining.BearingClusterGroup; +import com.sap.sse.datamining.data.Cluster; + +public class BearingClusterGroupTest { + + @Test + public void testBearingClusterGroupConstruction() { + BearingClusterGroup group = new BearingClusterGroup(-180, 180, 5); + for (int i = -179; i < 180; i++) { + Cluster cluster = group.getClusterFor(new DegreeBearingImpl(i)); + assertThat(cluster, notNullValue()); + } + + Cluster cluster1 = group.getClusterFor(new DegreeBearingImpl(-179)); + Cluster cluster2 = group.getClusterFor(new DegreeBearingImpl(-178)); + + assertTrue(cluster1.equals(cluster2)); + } +} diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java index 42f93938aad..f7f64ede35c 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/BearingClusterGroup.java @@ -24,12 +24,11 @@ public class BearingClusterGroup extends FixClusterGroup{ private static Collection> createClusters(int startAngle, int endAngle, int clusterSize) { int numberOfClusters = (endAngle - startAngle) / clusterSize; - double sizeOfOneCluster = clusterSize / numberOfClusters; List> clusters = new ArrayList>(); for (int i = 0; i < numberOfClusters; i++) { clusters.add(new ClusterWithLowerAndUpperBoundaries("", createBoundary(startAngle + i - * sizeOfOneCluster, ComparisonStrategy.GREATER_THAN), createBoundary(startAngle + (i + 1) - * sizeOfOneCluster, ComparisonStrategy.LOWER_EQUALS_THAN))); + * clusterSize, ComparisonStrategy.GREATER_THAN), createBoundary(startAngle + (i + 1) + * clusterSize, ComparisonStrategy.LOWER_EQUALS_THAN))); } return clusters; } From ae9ca81f455ff35318bfc42708029dc01d31000f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 09:37:41 +0100 Subject: [PATCH 190/264] fixed launch configs regarding zxing bundle; added Javadoc --- ...ingServer (No Proxy, Jetty on 8889).launch | 2 +- ...o Proxy, Jetty on 8889, Cached MTB).launch | 4 ++-- ...ingServer (No Proxy, Jetty on 8890).launch | 2 +- ...ver (No Proxy, Remote Debug SAP VM).launch | 4 ++-- ...ailingServer (No Proxy, winddbTest).launch | 2 +- ...ailingServer (Proxy, Jetty on 8889).launch | 2 +- ...Server (Proxy, Remote Debug SAP VM).launch | 4 ++-- ...Proxy, SwissTiming Live Simulation).launch | 2 +- .../SailingServer (Proxy, winddbTest).launch | 2 +- .../security/jaxrs/api/SecurityResource.java | 22 +++++++++++++------ 10 files changed, 27 insertions(+), 19 deletions(-) diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 3ec0f77dfc2..69a5e66fe14 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -23,7 +23,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index 7dc838a1181..68c47acb5b8 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -16,14 +16,14 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index 4e7742996a4..877c62f65a7 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -22,7 +22,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index a0b813374ec..04690b503fa 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -15,14 +15,14 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch index f14b6637666..c697f884a12 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -29,7 +29,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 6d2ff20591f..9b48f3ec5b0 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -23,7 +23,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index 90ea164e7fd..3f70919f0bc 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -16,13 +16,13 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index cfa760728db..5d64c2adeb8 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -22,7 +22,7 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 0f9e209026a..dc1d3ebe3e8 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -24,7 +24,7 @@ - + diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index ece7d27cbd2..179d3d0d0b6 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -14,10 +14,19 @@ import com.sap.sse.security.jaxrs.AbstractSecurityResource; @Path("/restsecurity") public class SecurityResource extends AbstractSecurityResource { + /** + * Can be used to figure out the current subject. Accepts the GET method. If the subject is + * authenticated, the service will respond with a "Hello <subjectname>" message, otherwise + * with a generic "Hello!". + */ @GET @Path("/hello") @Produces("text/plain;charset=UTF-8") public Response sayHello() { + return doSayHello(); + } + + private Response doSayHello() { final String messageText; if (SecurityUtils.getSubject().isAuthenticated()) { messageText = "Hello "+SecurityUtils.getSubject().getPrincipal(); @@ -27,17 +36,16 @@ public class SecurityResource extends AbstractSecurityResource { return Response.ok(messageText, MediaType.TEXT_PLAIN_TYPE).build(); } + /** + * Can be used to figure out the current subject. Accepts the POST method. If the subject is + * authenticated, the service will respond with a "Hello <subjectname>" message, otherwise + * with a generic "Hello!". + */ @POST @Path("/hello") @Produces("text/plain;charset=UTF-8") public Response sayHelloPost() { - final String messageText; - if (SecurityUtils.getSubject().isAuthenticated()) { - messageText = "Hello "+SecurityUtils.getSubject().getPrincipal(); - } else { - messageText = "Hello!"; - } - return Response.ok(messageText, MediaType.TEXT_PLAIN_TYPE).build(); + return doSayHello(); } @GET From b6de344ee81f4980bca7f327cd5925cf3bb427dd Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 09:51:07 +0100 Subject: [PATCH 191/264] removed basic authentication from test environment URLs; during authenticating disable forwarding --- java/com.sap.sailing.selenium.test/ci-test-environment.xml | 2 +- .../com.sap.sailing.selenium.test/local-test-environment.xml | 2 +- .../com/sap/sailing/selenium/test/AbstractSeleniumTest.java | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.selenium.test/ci-test-environment.xml b/java/com.sap.sailing.selenium.test/ci-test-environment.xml index 8dc260e081a..048f5f00236 100755 --- a/java/com.sap.sailing.selenium.test/ci-test-environment.xml +++ b/java/com.sap.sailing.selenium.test/ci-test-environment.xml @@ -13,7 +13,7 @@ - http://riccardo:riccardo@localhost:8085/ + http://localhost:8085/ - http://riccardo:riccardo@localhost:8888/ + http://localhost:8888/ ./bin/surefire-reports/ diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java index acaaa4e2892..40bdfbf8248 100755 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/AbstractSeleniumTest.java @@ -114,12 +114,13 @@ public abstract class AbstractSeleniumTest { Cookie result = null; URL url = new URL(contextRoot + LOGIN_URL); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setInstanceFollowRedirects(false); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.connect(); connection.getOutputStream().write("username=admin&password=admin".getBytes()); - if (connection.getResponseCode() != 200) { - throw new RuntimeException(connection.getResponseMessage()); + if (connection.getResponseCode() / 100 != 3) { // expecting something like "302 Found" which redirects to the success page + throw new RuntimeException("" + connection.getResponseCode() + " "+connection.getResponseMessage()); } List cookies = connection.getHeaderFields().get("Set-Cookie"); if (cookies != null) { From 6ad8d929816019cf7e4b512c6b3e0ca0425767d3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 12:28:23 +0100 Subject: [PATCH 192/264] don't save the username in the SOCIAL_USER field; it can always be obtained by Subject.getPrincipal() --- .../ui/server/UserManagementServiceImpl.java | 5 ++--- .../src/com/sap/sse/security/OAuthRealm.java | 2 -- .../com/sap/sse/security/SessionUtils.java | 11 ---------- .../security/impl/SecurityServiceImpl.java | 20 +++++++++---------- 4 files changed, 11 insertions(+), 27 deletions(-) diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index 9505f3b9f98..c099a296bb2 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -30,7 +30,6 @@ import com.sap.sse.common.Util; import com.sap.sse.common.mail.MailException; import com.sap.sse.security.Credential; import com.sap.sse.security.SecurityService; -import com.sap.sse.security.SessionUtils; import com.sap.sse.security.Social; import com.sap.sse.security.User; import com.sap.sse.security.shared.Account; @@ -117,7 +116,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public SuccessInfo logout() { - logger.info("Logging out user: " + SessionUtils.loadUsername()); + logger.info("Logging out user: " + SecurityUtils.getSubject().getPrincipal().toString()); getSecurityService().logout(); getHttpSession().invalidate(); logger.info("Invalidated HTTP session"); @@ -167,7 +166,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public void updateSimpleUserEmail(String username, String newEmail, String validationBaseURL) throws UserManagementException, MailException { final Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.getRolename()) || username.equals(SessionUtils.loadUsername())) { + if (subject.hasRole(DefaultRoles.ADMIN.getRolename()) || username.equals(SecurityUtils.getSubject().getPrincipal().toString())) { getSecurityService().updateSimpleUserEmail(username, newEmail, validationBaseURL); } else { throw new UserManagementException(UserManagementException.INVALID_CREDENTIALS); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java index 9326ddf7ac3..0e4169c932d 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/OAuthRealm.java @@ -185,8 +185,6 @@ public class OAuthRealm extends AbstractUserStoreBasedRealm { throw new AuthenticationException(e.getMessage()); } } - SessionUtils.saveUsername(user.getName()); - SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo(); SimplePrincipalCollection spc = new SimplePrincipalCollection(); spc.add(otoken.getPrincipal(), otoken.getPrincipal().toString()); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SessionUtils.java b/java/com.sap.sse.security/src/com/sap/sse/security/SessionUtils.java index df20952a862..f5c56739427 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SessionUtils.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SessionUtils.java @@ -113,17 +113,6 @@ public class SessionUtils { session.setAttribute(SOCIAL_USER, username); } - public static String loadUsername() { - final String result; - Session session = SecurityUtils.getSubject().getSession(); - if (session == null) { - result = null; - } else { - result = (String) session.getAttribute(SOCIAL_USER); - } - return result; - } - public static void saveSessionIdToSession(String sessionId) throws AuthenticationException { Session session = SecurityUtils.getSubject().getSession(); if (session == null) { diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index a82d3fac632..3e0a585c3c8 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -277,7 +277,6 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica logger.info("Trying to login: " + username); Subject subject = SecurityUtils.getSubject(); subject.login(token); - SessionUtils.saveUsername(username); HttpServletRequest httpRequest = WebUtils.getHttpRequest(subject); SavedRequest savedRequest = WebUtils.getSavedRequest(httpRequest); if (savedRequest != null) { @@ -297,7 +296,6 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica try { subject.login(token); final String username = (String) token.getPrincipal(); - SessionUtils.saveUsername(username); return store.getUserByName(username); } catch (AuthenticationException e) { logger.log(Level.INFO, "Authentication failed with access token "+accessToken); @@ -597,7 +595,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica if (!subject.isAuthenticated()) { try { subject.login(otoken); - logger.info("User [" + SessionUtils.loadUsername() + "] logged in successfully."); + logger.info("User [" + SecurityUtils.getSubject().getPrincipal().toString() + "] logged in successfully."); } catch (UnknownAccountException uae) { logger.info("There is no user with username of " + subject.getPrincipal()); throw new UserManagementException("Invalid credentials!"); @@ -613,7 +611,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica throw new UserManagementException("An error occured while authenticating the user!"); } } - String username = SessionUtils.loadUsername(); + String username = SecurityUtils.getSubject().getPrincipal().toString(); if (username == null) { logger.info("Something went wrong while authneticating, check doGetAuthenticationInfo() in " + OAuthRealm.class.getName() + "."); @@ -634,7 +632,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica if (subject == null || !subject.isAuthenticated()) { result = null; } else { - String username = SessionUtils.loadUsername(); + String username = SecurityUtils.getSubject().getPrincipal().toString(); if (username == null || username.length() <= 0) { result = null; } else { @@ -873,10 +871,10 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica @Override public void setPreference(final String username, final String key, final String value) { final Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SessionUtils.loadUsername())) { + if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SecurityUtils.getSubject().getPrincipal().toString())) { apply(s->s.internalSetPreference(username, key, value)); } else { - throw new SecurityException("User " + SessionUtils.loadUsername() + throw new SecurityException("User " + SecurityUtils.getSubject().getPrincipal().toString() + " does not have permission to set preference for user " + username); } } @@ -890,10 +888,10 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica @Override public void unsetPreference(String username, String key) { Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SessionUtils.loadUsername())) { + if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SecurityUtils.getSubject().getPrincipal().toString())) { apply(s->s.internalUnsetPreference(username, key)); } else { - throw new SecurityException("User " + SessionUtils.loadUsername() + throw new SecurityException("User " + SecurityUtils.getSubject().getPrincipal().toString() + " does not have permission to unset preference for user " + username); } } @@ -919,10 +917,10 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica @Override public String getPreference(String username, String key) { Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SessionUtils.loadUsername())) { + if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SecurityUtils.getSubject().getPrincipal().toString())) { return store.getPreference(username, key); } else { - throw new SecurityException("User " + SessionUtils.loadUsername() + throw new SecurityException("User " + SecurityUtils.getSubject().getPrincipal().toString() + " does not have permission to read preferences of user " + username); } } From 837fce4a6cc6ec91b84cf6da88861fd60d78953a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 12:32:01 +0100 Subject: [PATCH 193/264] re-use subject instead of acquiring it freshly where possible --- .../ui/server/UserManagementServiceImpl.java | 2 +- .../sse/security/impl/SecurityServiceImpl.java | 18 +++++++++--------- .../security/jaxrs/api/SecurityResource.java | 6 ++++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index c099a296bb2..9ebd35a67f2 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -166,7 +166,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public void updateSimpleUserEmail(String username, String newEmail, String validationBaseURL) throws UserManagementException, MailException { final Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.getRolename()) || username.equals(SecurityUtils.getSubject().getPrincipal().toString())) { + if (subject.hasRole(DefaultRoles.ADMIN.getRolename()) || username.equals(subject.getPrincipal().toString())) { getSecurityService().updateSimpleUserEmail(username, newEmail, validationBaseURL); } else { throw new UserManagementException(UserManagementException.INVALID_CREDENTIALS); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index 3e0a585c3c8..908108984d3 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -595,7 +595,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica if (!subject.isAuthenticated()) { try { subject.login(otoken); - logger.info("User [" + SecurityUtils.getSubject().getPrincipal().toString() + "] logged in successfully."); + logger.info("User [" + subject.getPrincipal().toString() + "] logged in successfully."); } catch (UnknownAccountException uae) { logger.info("There is no user with username of " + subject.getPrincipal()); throw new UserManagementException("Invalid credentials!"); @@ -611,7 +611,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica throw new UserManagementException("An error occured while authenticating the user!"); } } - String username = SecurityUtils.getSubject().getPrincipal().toString(); + String username = subject.getPrincipal().toString(); if (username == null) { logger.info("Something went wrong while authneticating, check doGetAuthenticationInfo() in " + OAuthRealm.class.getName() + "."); @@ -632,7 +632,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica if (subject == null || !subject.isAuthenticated()) { result = null; } else { - String username = SecurityUtils.getSubject().getPrincipal().toString(); + String username = subject.getPrincipal().toString(); if (username == null || username.length() <= 0) { result = null; } else { @@ -871,10 +871,10 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica @Override public void setPreference(final String username, final String key, final String value) { final Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SecurityUtils.getSubject().getPrincipal().toString())) { + if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(subject.getPrincipal().toString())) { apply(s->s.internalSetPreference(username, key, value)); } else { - throw new SecurityException("User " + SecurityUtils.getSubject().getPrincipal().toString() + throw new SecurityException("User " + subject.getPrincipal().toString() + " does not have permission to set preference for user " + username); } } @@ -888,10 +888,10 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica @Override public void unsetPreference(String username, String key) { Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SecurityUtils.getSubject().getPrincipal().toString())) { + if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(subject.getPrincipal().toString())) { apply(s->s.internalUnsetPreference(username, key)); } else { - throw new SecurityException("User " + SecurityUtils.getSubject().getPrincipal().toString() + throw new SecurityException("User " + subject.getPrincipal().toString() + " does not have permission to unset preference for user " + username); } } @@ -917,10 +917,10 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica @Override public String getPreference(String username, String key) { Subject subject = SecurityUtils.getSubject(); - if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(SecurityUtils.getSubject().getPrincipal().toString())) { + if (subject.hasRole(DefaultRoles.ADMIN.name()) || username.equals(subject.getPrincipal().toString())) { return store.getPreference(username, key); } else { - throw new SecurityException("User " + SecurityUtils.getSubject().getPrincipal().toString() + throw new SecurityException("User " + subject.getPrincipal().toString() + " does not have permission to read preferences of user " + username); } } diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index 179d3d0d0b6..11f592b64a4 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -8,6 +8,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; import org.json.simple.JSONObject; import com.sap.sse.security.jaxrs.AbstractSecurityResource; @@ -28,8 +29,9 @@ public class SecurityResource extends AbstractSecurityResource { private Response doSayHello() { final String messageText; - if (SecurityUtils.getSubject().isAuthenticated()) { - messageText = "Hello "+SecurityUtils.getSubject().getPrincipal(); + final Subject subject = SecurityUtils.getSubject(); + if (subject.isAuthenticated()) { + messageText = "Hello "+subject.getPrincipal(); } else { messageText = "Hello!"; } From 21f76c6dc4fba954bb7c40d96d3607de01230912 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 14:36:01 +0100 Subject: [PATCH 194/264] obsolete old bearer access token when a new one is obtained for the subject --- .../com.sap.sailing.server.gateway/WEB-INF/web.xml | 14 +++++++------- .../security/jaxrs/api/SecurityResourceTest.java | 9 +++++++++ .../security/userstore/mongodb/UserStoreImpl.java | 4 ++++ java/com.sap.sse.security/build.properties | 3 ++- .../sse/security/jaxrs/api/SecurityResource.java | 14 ++++++-------- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml index 95efad80403..0626a974f2a 100644 --- a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml @@ -253,12 +253,12 @@ com.sap.sailing.server.gateway.jaxrs.AccessControlAndEncodingResponseFilter 1 - - + + Jersey REST API /api/* - - + + Jersey REST SPI com.sap.sailing.server.gateway.jaxrs.RestServletContainer @@ -270,10 +270,10 @@ com.sap.sailing.server.gateway.jaxrs.AccessControlAndEncodingResponseFilter 1 - - + + Jersey REST SPI /spi/* - + \ No newline at end of file diff --git a/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java b/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java index b1ddc5ff77e..53eb451d6fd 100755 --- a/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java +++ b/java/com.sap.sse.security.test/src/com/sap/sse/security/jaxrs/api/SecurityResourceTest.java @@ -3,6 +3,7 @@ package com.sap.sse.security.jaxrs.api; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.apache.shiro.SecurityUtils; @@ -84,4 +85,12 @@ public class SecurityResourceTest { subject.logout(); assertFalse(subject.isAuthenticated()); } + + @Test + public void ensureOldBearerTokenIsInvalidatedByObtainingNewOne() throws ParseException { + String accessToken = createAccessToken(); + createAccessToken(); + User user = service.getUserByAccessToken(accessToken); + assertNull(user); // the old access token is expected to have been obsoleted by obtaining a new one + } } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java index 28deb3c062f..db19c10abea 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java @@ -117,6 +117,10 @@ public class UserStoreImpl implements UserStore { result = false; } else { result = true; + final String oldAccessToken = getPreference(username, ACCESS_TOKEN_KEY); + if (oldAccessToken != null) { + usersByAccessToken.remove(oldAccessToken); + } usersByAccessToken.put(accessToken, user); setPreference(username, ACCESS_TOKEN_KEY, accessToken); } diff --git a/java/com.sap.sse.security/build.properties b/java/com.sap.sse.security/build.properties index d74e5aa7be3..29ace3f1e22 100644 --- a/java/com.sap.sse.security/build.properties +++ b/java/com.sap.sse.security/build.properties @@ -4,4 +4,5 @@ output.. = bin/ bin.includes = META-INF/,\ .,\ WEB-INF/,\ - resources/shiro.ini + resources/shiro.ini,\ + webservices/ diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java index 11f592b64a4..cb83d6e4086 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/jaxrs/api/SecurityResource.java @@ -22,20 +22,18 @@ public class SecurityResource extends AbstractSecurityResource { */ @GET @Path("/hello") - @Produces("text/plain;charset=UTF-8") + @Produces("application/json;charset=UTF-8") public Response sayHello() { return doSayHello(); } private Response doSayHello() { - final String messageText; final Subject subject = SecurityUtils.getSubject(); - if (subject.isAuthenticated()) { - messageText = "Hello "+subject.getPrincipal(); - } else { - messageText = "Hello!"; - } - return Response.ok(messageText, MediaType.TEXT_PLAIN_TYPE).build(); + final JSONObject result = new JSONObject(); + result.put("principal", subject.getPrincipal().toString()); + result.put("authenticated", subject.isAuthenticated()); + result.put("remembered", subject.isRemembered()); + return Response.ok(result.toJSONString(), MediaType.APPLICATION_JSON_TYPE).build(); } /** From 05749b3778d665f8c8bc1e9138180812673a7d18 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Wed, 25 Feb 2015 14:19:18 +0000 Subject: [PATCH 195/264] Improved the formatting --- wiki/local-gwt.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wiki/local-gwt.md b/wiki/local-gwt.md index 0690be0ae51..965bb6e2217 100644 --- a/wiki/local-gwt.md +++ b/wiki/local-gwt.md @@ -33,5 +33,7 @@ Note the reduced performance which is largely due to the way the Java VM and the ## Curious Errors There's a randomly occuring error, since the update to GWT 2.7, where an entry point fails to load. The Development Mode view of Eclipse shows the error -**Your source appears not to live underneath a subpackage called 'client'; no problem, but you'll need to use the <source> directive in your module to make it accessible** +

      +Your source appears not to live underneath a subpackage called 'client'; no problem, but you'll need to use the <source> directive in your module to make it accessible
      +
      The problem can be "solved" by retrying until it works. \ No newline at end of file From 6c5b4e9027d96524e9ca1d694c98d577daea1f14 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 17:22:23 +0100 Subject: [PATCH 196/264] added documentation for the security REST API --- java/com.sap.sse.security/WEB-INF/web.xml | 6 +- java/com.sap.sse.security/resources/shiro.ini | 4 +- .../webservices/api/access_token.html | 60 +++++++++++++++++++ .../webservices/api/hello.html | 58 ++++++++++++++++++ .../webservices/api/index.html | 48 +++++++++++++++ .../webservices/api/login.html | 44 ++++++++++++++ .../webservices/api/logout.html | 44 ++++++++++++++ 7 files changed, 261 insertions(+), 3 deletions(-) create mode 100755 java/com.sap.sse.security/webservices/api/access_token.html create mode 100755 java/com.sap.sse.security/webservices/api/hello.html create mode 100755 java/com.sap.sse.security/webservices/api/index.html create mode 100755 java/com.sap.sse.security/webservices/api/login.html create mode 100755 java/com.sap.sse.security/webservices/api/logout.html diff --git a/java/com.sap.sse.security/WEB-INF/web.xml b/java/com.sap.sse.security/WEB-INF/web.xml index 418dbcc8b1a..42b7b94eabd 100644 --- a/java/com.sap.sse.security/WEB-INF/web.xml +++ b/java/com.sap.sse.security/WEB-INF/web.xml @@ -13,7 +13,11 @@ dirAllowed false - + + useFileMappedBuffer + false + + shiroEnvironmentClass diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index 43a161f34a9..4c2965ca841 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -21,7 +21,8 @@ customGwt = com.sap.sse.security.CustomFilter customGwt.loginUrl = /security/ui/Login.html customGwt.successUrl = /UserManagement.html -authc.loginUrl=/api/restsecurity/login +authc.loginUrl = /api/restsecurity/login +authc.successUrl = /api/restsecurity/hello bearerToken = com.sap.sse.security.BearerTokenAuthenticationFilter @@ -36,4 +37,3 @@ securityManager.cacheManager = $cacheManager /api/restsecurity/logout = logout /api/restsecurity/access_token = bearerToken /api/restsecurity/hello = bearerToken -/api/restsecurity/hello_post = bearerToken diff --git a/java/com.sap.sse.security/webservices/api/access_token.html b/java/com.sap.sse.security/webservices/api/access_token.html new file mode 100755 index 00000000000..e8eadadfd30 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/access_token.html @@ -0,0 +1,60 @@ + + + + + + + + SAP Sponsorships Engine Security Webservices API + + +

      SAP Sponsorships Engine Security Webservices API

      +

      URL: /api/restsecurity/access_token

      + +Description: +

      Obtains a bearer access token that can be used to authenticate the subject making this request during +future requests. All bearer access tokens for this subject that have been obtained earlier are invalidated +by this request. The subject can be authenticated by means of +a session ID given as the value for the JSESSIONID cookie. Alternatively, the subject can be authenticated using +the username and password POST parameters. Subject authentication may also +work by providing a bearer access token as obtained by an earlier call to this +service end point. The bearer token has to be provided as an HTTP request header field "Authorization: Bearer <bearer-token>" +Lastly, basic authentication can be used where technically the username and password are provided as base64-encoded string in +the "Authorization:" header field like this: "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==". Most REST clients use +shorthand URL notations with ":" and "@" to denote basic authentication, with these URL elements not being logged.

      +
      + + + + + + + + + + + + + + + + + +
      Request Method:GET/POST
      Output format:JSON document of the following format
      +
      {"username":"admin","access_token":"tnN1yVnXRuqEce3JqMpLUl7d+qeofez0tljKU1KzALw="}
      +
      Mandatory parameters:None
      Examples: +
      Using the session ID in a session cookie:
      + curl -D /tmp/header -b JSESSIONID=5e94c93e-b515-4bcd-8df8-0c786805a9e0 http://127.0.0.1:8888/security/api/restsecurity/access_token +
      +
      Using POST username and password form parameters:
      + curl -d "username=admin&password=admin"-D /tmp/header http://127.0.0.1:8888/security/api/restsecurity/access_token +
      +
      Using a bearer access token:
      + curl -D /tmp/header -H "Authorization: Bearer MVPg/uCV1QGqMDmDtUuOQBrSrB1NlIldm0LW+dnxZ2A=" http://127.0.0.1:8888/security/api/restsecurity/access_token +
      +
      Using basic authentication:
      + curl -D /tmp/header http://admin:admin@127.0.0.1:8888/security/api/restsecurity/access_token +
      +
      + + diff --git a/java/com.sap.sse.security/webservices/api/hello.html b/java/com.sap.sse.security/webservices/api/hello.html new file mode 100755 index 00000000000..f8b5d9d77c3 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/hello.html @@ -0,0 +1,58 @@ + + + + + + + + SAP Sponsorships Engine Security Webservices API + + +

      SAP Sponsorships Engine Security Webservices API

      +

      URL: /api/restsecurity/hello

      + +Description: +

      Shows the currently authenticated subject and its properties. The subject can be authenticated by means of +a session ID given as the value for the JSESSIONID cookie. Alternatively, the subject can be authenticated using +the username and password POST parameters. Subject authentication may also +work by providing a bearer access token as obtained by the access_token +service end point. The bearer token has to be provided as an HTTP request header field "Authorization: Bearer <bearer-token>" +Lastly, basic authentication can be used where technically the username and password are provided as base64-encoded string in +the "Authorization:" header field like this: "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==". Most REST clients use +shorthand URL notations with ":" and "@" to denote basic authentication, with these URL elements not being logged.

      +
      + + + + + + + + + + + + + + + + + +
      Request Method:GET/POST
      Output format:JSON document of the following format
      +
      {"principal":"admin","authenticated":true,"remembered":false}
      +
      Mandatory parameters:None
      Examples: +
      Using the session ID in a session cookie:
      + curl -D /tmp/header -b JSESSIONID=5e94c93e-b515-4bcd-8df8-0c786805a9e0 http://127.0.0.1:8888/security/api/restsecurity/hello +
      +
      Using POST username and password form parameters:
      + curl -d "username=admin&password=admin"-D /tmp/header http://127.0.0.1:8888/security/api/restsecurity/hello +
      +
      Using a bearer access token:
      + curl -D /tmp/header -H "Authorization: Bearer MVPg/uCV1QGqMDmDtUuOQBrSrB1NlIldm0LW+dnxZ2A=" http://127.0.0.1:8888/security/api/restsecurity/hello +
      +
      Using basic authentication:
      + curl -D /tmp/header http://admin:admin@127.0.0.1:8888/security/api/restsecurity/hello +
      +
      + + diff --git a/java/com.sap.sse.security/webservices/api/index.html b/java/com.sap.sse.security/webservices/api/index.html new file mode 100755 index 00000000000..ed37dfd0078 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/index.html @@ -0,0 +1,48 @@ + + + + + + + + SAP Sailing Analytics Security Webservices API + + +

      SAP Sport Sponsorships Engine Security Webservices API - Overview

      + +

      +The SAP Sport Sponsorships Engine (SSE) and the applications built with it support security with +various authentication and authorization facilities. This module offers REST services that can be +used to log in and log out a subject, obtain a bearer token for a subject and manage security concerns. +By default, the base URL for these services is "/security", so a full login URL, for example, could +be http://sapsailing.com/security/api/restsecurity/login. +

      +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      Webservice URLDescription
      /api/restsecurity/loginLog in a subject
      /api/restsecurity/logoutLog out a subject
      /api/restsecurity/helloTest that a subject is authenticated
      /api/restsecurity/access_tokenObtain a bearer access token
      + + \ No newline at end of file diff --git a/java/com.sap.sse.security/webservices/api/login.html b/java/com.sap.sse.security/webservices/api/login.html new file mode 100755 index 00000000000..aceb9509686 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/login.html @@ -0,0 +1,44 @@ + + + + + + + + SAP Sponsorships Engine Security Webservices API + + +

      SAP Sponsorships Engine Security Webservices API

      +

      URL: /api/restsecurity/login

      + +Description: +

      The service authenticates a subject based on POST form parameter and creates a session. + The session is represented by the JSESSIONID cookie value which can be used in subsequent + requests to the same session with the same authenticated subject.

      +
      + + + + + + + + + + + + + + + + + +
      Request Method:POST
      Output format:Redirect (302 Found) to /api/restsecurity/hello which delivers a JSON document of the following format
      +
      {"principal":"admin","authenticated":true,"remembered":false}
      +
      Mandatory parameters: +
      username
      +
      password
      +
      Example:curl -L -D /tmp/header -d "username=admin&password=admin" http://127.0.0.1:8888/security/api/restsecurity/login
      + Will provide a redirect and a JSON document as explained above.
      + + diff --git a/java/com.sap.sse.security/webservices/api/logout.html b/java/com.sap.sse.security/webservices/api/logout.html new file mode 100755 index 00000000000..6324bef6a13 --- /dev/null +++ b/java/com.sap.sse.security/webservices/api/logout.html @@ -0,0 +1,44 @@ + + + + + + + + SAP Sponsorships Engine Security Webservices API + + +

      SAP Sponsorships Engine Security Webservices API

      +

      URL: /api/restsecurity/logout

      + +Description: +

      The service terminates a session, based on the JSESSIONID cookie. The session's subject is no longer +considered authenticated and the JSESSIONID used to make the request is considered invalid from now on.

      +
      + + + + + + + + + + + + + + + + + +
      Request Method:GET/POST
      Output format:Redirect (302 Found) to / which is not expected to deliver anything +
      Mandatory parameters:None
      Example:curl -D /tmp/header -b JSESSIONID=5e94c93e-b515-4bcd-8df8-0c786805a9e0 http://127.0.0.1:8888/security/api/restsecurity/logout
      + Will return something like
      
      +	            HTTP/1.1 302 Found
      +		    Set-Cookie: rememberMe=deleteMe; Path=/security; Max-Age=0; Expires=Tue, 24-Feb-2015 15:58:16 GMT
      +		    Set-Cookie: JSESSIONID=deleteMe; Path=/; Max-Age=0; Expires=Tue, 24-Feb-2015 15:58:16 GMT
      +		    Location: http://127.0.0.1:8888/security/
      +		    Content-Length: 0
      + + From d053c394dd3df8086850d902a924856b09f44c38 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 18:25:19 +0100 Subject: [PATCH 197/264] avoid NPE during logout --- .../sap/sse/security/ui/server/UserManagementServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index 9ebd35a67f2..c88046d4581 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -116,7 +116,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U @Override public SuccessInfo logout() { - logger.info("Logging out user: " + SecurityUtils.getSubject().getPrincipal().toString()); + logger.info("Logging out user: " + SecurityUtils.getSubject()); getSecurityService().logout(); getHttpSession().invalidate(); logger.info("Invalidated HTTP session"); From c196f48ad1708d71ad99e2bf0f43acb613cb30f1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 25 Feb 2015 18:26:27 +0100 Subject: [PATCH 198/264] prepared the com.sap.sailing.server.gateway bundle for REST security, showing a commented example in shiro.ini for the /events service end point --- .../com.sap.sailing.server.gateway/.classpath | 15 ++++---- .../META-INF/MANIFEST.MF | 6 ++- .../WEB-INF/web.xml | 24 ++++++++++++ .../build.properties | 3 +- .../resources/shiro.ini | 37 +++++++++++++++++++ java/com.sap.sse.security/resources/shiro.ini | 2 +- .../security/AbstractUserStoreBasedRealm.java | 1 + ...kenOrBasicOrFormAuthenticationFilter.java} | 4 +- 8 files changed, 80 insertions(+), 12 deletions(-) create mode 100755 java/com.sap.sailing.server.gateway/resources/shiro.ini rename java/com.sap.sse.security/src/com/sap/sse/security/{BearerTokenAuthenticationFilter.java => BearerTokenOrBasicOrFormAuthenticationFilter.java} (92%) diff --git a/java/com.sap.sailing.server.gateway/.classpath b/java/com.sap.sailing.server.gateway/.classpath index eca7bdba8f0..1747f410a23 100644 --- a/java/com.sap.sailing.server.gateway/.classpath +++ b/java/com.sap.sailing.server.gateway/.classpath @@ -1,7 +1,8 @@ - - - - - - - + + + + + + + + diff --git a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF index 41fcf805f1d..12fe41895b9 100644 --- a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF @@ -48,7 +48,11 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.server.gateway.serialization.shared.android, com.sap.sse.shared.android, com.sap.sse.filestorage, - com.sun.jersey.contribs.jersey-multipart;bundle-version="1.17.1" + com.sun.jersey.contribs.jersey-multipart;bundle-version="1.17.1", + com.sap.sse.security, + 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" Bundle-ClassPath: . Web-ContextPath: /sailingserver Export-Package: com.sap.sailing.server.gateway, diff --git a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml index 0626a974f2a..138f4b72d80 100644 --- a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml @@ -16,6 +16,30 @@ + + + shiroEnvironmentClass + org.apache.shiro.web.env.IniWebEnvironment + + + org.apache.shiro.web.env.EnvironmentLoaderListener + + + ShiroFilter + org.apache.shiro.web.servlet.ShiroFilter + + + + ShiroFilter + /* + REQUEST + FORWARD + INCLUDE + ERROR + + AdminApp com.sap.sailing.server.gateway.impl.AdminApp diff --git a/java/com.sap.sailing.server.gateway/build.properties b/java/com.sap.sailing.server.gateway/build.properties index 2c55f73f136..d178f8e4452 100644 --- a/java/com.sap.sailing.server.gateway/build.properties +++ b/java/com.sap.sailing.server.gateway/build.properties @@ -1,4 +1,5 @@ -source.. = src/ +source.. = src/,\ + resources/ output.. = bin/ bin.includes = META-INF/,\ .,\ diff --git a/java/com.sap.sailing.server.gateway/resources/shiro.ini b/java/com.sap.sailing.server.gateway/resources/shiro.ini new file mode 100755 index 00000000000..10e91bb81bd --- /dev/null +++ b/java/com.sap.sailing.server.gateway/resources/shiro.ini @@ -0,0 +1,37 @@ + +[main] +# Use Built-in Cache Manager +credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher + +# base64 encoding, not hex in this example: +credentialsMatcher.storedCredentialsHexEncoded = false +credentialsMatcher.hashIterations = 1024 + +upRealm = com.sap.sse.security.UsernamePasswordRealm +upRealm.credentialsMatcher = $credentialsMatcher +oauthRealm = com.sap.sse.security.OAuthRealm +bearerTokenRealm = com.sap.sse.security.BearerTokenRealm + +securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm + +sessionManager = com.sap.sse.security.SecurityWebSessionManager +securityManager.sessionManager = $sessionManager + +customGwt = com.sap.sse.security.CustomFilter +customGwt.loginUrl = /security/ui/Login.html +customGwt.successUrl = /UserManagement.html + +authc.loginUrl = /api/restsecurity/login +authc.successUrl = /api/restsecurity/hello + +bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter + +sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO +securityManager.sessionManager.sessionDAO = $sessionDAO + +cacheManager = com.sap.sse.security.SessionCacheManager +securityManager.cacheManager = $cacheManager + +[urls] +# Uncomment the following to require the permission "event:view" in order to use /api/v1/events: +# /api/v1/events = bearerToken, perms["event:view"] diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index 4c2965ca841..ae62d9f5c5d 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -24,7 +24,7 @@ customGwt.successUrl = /UserManagement.html authc.loginUrl = /api/restsecurity/login authc.successUrl = /api/restsecurity/hello -bearerToken = com.sap.sse.security.BearerTokenAuthenticationFilter +bearerToken = com.sap.sse.security.BearerTokenOrBasicOrFormAuthenticationFilter sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO securityManager.sessionManager.sessionDAO = $sessionDAO diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java index f08dce240e7..0c747d3e106 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/AbstractUserStoreBasedRealm.java @@ -41,6 +41,7 @@ public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm { public AbstractUserStoreBasedRealm() { super(); + setCachingEnabled(false); // always grab fresh authorization info from the user store BundleContext context = Activator.getContext(); if (context != null) { userStore = createUserStoreFuture(context); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenOrBasicOrFormAuthenticationFilter.java similarity index 92% rename from java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java rename to java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenOrBasicOrFormAuthenticationFilter.java index ef0340a180f..38039aba967 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenAuthenticationFilter.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/BearerTokenOrBasicOrFormAuthenticationFilter.java @@ -27,7 +27,7 @@ import com.sap.sse.security.jaxrs.api.SecurityResource; * @author Axel Uhl (D043530) * */ -public class BearerTokenAuthenticationFilter extends BasicHttpAuthenticationFilter { +public class BearerTokenOrBasicOrFormAuthenticationFilter extends BasicHttpAuthenticationFilter { private static final String BEARER = "Bearer"; @@ -37,7 +37,7 @@ public class BearerTokenAuthenticationFilter extends BasicHttpAuthenticationFilt */ private final FormAuthenticationFilterWithPublicCreateToken formAuthenticationFilter; - public BearerTokenAuthenticationFilter() { + public BearerTokenOrBasicOrFormAuthenticationFilter() { formAuthenticationFilter = new FormAuthenticationFilterWithPublicCreateToken(); } From 4d7278664cea1861ba8a6b65ee15fd4297ea4b98 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 08:26:18 +0000 Subject: [PATCH 199/264] Created security (markdown) --- wiki/security.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 wiki/security.md diff --git a/wiki/security.md b/wiki/security.md new file mode 100644 index 00000000000..12e07f57af6 --- /dev/null +++ b/wiki/security.md @@ -0,0 +1,10 @@ +# Security + +The Sports Sponsorships Engine (SSE) on which the SAP Sailing Analytics and the SAP Tennis Analytics are based, uses Apache Shiro to implement security. This in particular includes authentication and authorization. + +## Users, Roles, and Permissions + + + +## How to Configure + From 2d26bc19f09123598a507b5f28c1c64cb7b3c6a3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 08:29:50 +0000 Subject: [PATCH 200/264] Updated security (markdown) --- wiki/security.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wiki/security.md b/wiki/security.md index 12e07f57af6..f87d5b12c2a 100644 --- a/wiki/security.md +++ b/wiki/security.md @@ -2,9 +2,11 @@ The Sports Sponsorships Engine (SSE) on which the SAP Sailing Analytics and the SAP Tennis Analytics are based, uses Apache Shiro to implement security. This in particular includes authentication and authorization. -## Users, Roles, and Permissions +## Users, Sessions, Roles, and Permissions +Users are identified in an authentication process. This can be a username/password login implemented by posting a form, through HTTP basic authentication (an "Authorization: Basic " HTTP header field) or by using an OAuth-like bearer access token that can be obtained by an authenticated user through a RESTful web service. +The response for an HTTP request by an authenticated user contains the JSESSIONID cookie whose value is the session key. Our sessions have a default timeout of 30 minutes (see ## How to Configure From 4faf7ea686cc75f4d55dde51dcb88bed4d160f2b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 08:45:26 +0000 Subject: [PATCH 201/264] Updated security (markdown) --- wiki/security.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/wiki/security.md b/wiki/security.md index f87d5b12c2a..9e4c3777d11 100644 --- a/wiki/security.md +++ b/wiki/security.md @@ -1,12 +1,20 @@ # Security -The Sports Sponsorships Engine (SSE) on which the SAP Sailing Analytics and the SAP Tennis Analytics are based, uses Apache Shiro to implement security. This in particular includes authentication and authorization. +The Sports Sponsorships Engine (SSE) on which the SAP Sailing Analytics and the SAP Tennis Analytics are based, uses Apache Shiro to implement security. This in particular includes authentication and authorization. This document does not aim to replace the Shiro documentation which is available, e.g., at [http://shiro.apache.org/configuration.html](http://shiro.apache.org/configuration.html), [http://shiro.apache.org/permissions.html](http://shiro.apache.org/permissions.html) and [http://shiro.apache.org/web.html](http://shiro.apache.org/web.html). ## Users, Sessions, Roles, and Permissions -Users are identified in an authentication process. This can be a username/password login implemented by posting a form, through HTTP basic authentication (an "Authorization: Basic " HTTP header field) or by using an OAuth-like bearer access token that can be obtained by an authenticated user through a RESTful web service. +Users are identified in an authentication process. This can currently be a username/password login implemented by posting a form, through HTTP basic authentication (an "Authorization: Basic " HTTP header field) or by using an OAuth-like bearer access token that can be obtained by an authenticated user through a RESTful web service. OAuth-based authentication by external OAuth providers and SAP ID is planned (see [Bug 2482](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2482)). -The response for an HTTP request by an authenticated user contains the JSESSIONID cookie whose value is the session key. Our sessions have a default timeout of 30 minutes (see +The response for an HTTP request by an authenticated user contains the JSESSIONID cookie whose value is the session key. Our sessions have a default timeout of 30 minutes which is provided as the Shiro default (see `org.apache.shiro.session.mgt.DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT` and its use in the `org.apache.shiro.session.mgt.SimpleSession` constructor). Each request performed with this session key will renew the session timeout. + +Logging out, e.g., by invoking the `/security/api/restsecurity/logout` service, invalidates the session key, and a new authentication will be required to obtain a new session. + +An authenticated user has zero or more roles and zero or more immediate permissions assigned. Roles, in turn, can imply more permissions which are added to the immediate permissions to result in the complete set of permissions the user has. Roles and immediate permissions are stored persistently in an instance of `com.sap.sse.security.UserStore` and can be dynamically adjusted during application run-time. + +The inference from roles to the permissions implied by those roles happens by implementations of the `com.sap.sse.security.shared.PermissionsForRoleProvider` interface. Authentication is mainly performed by so-called "Realm" implementations. SSE provides an abstract base class `com.sap.sse.security.AbstractUserStoreBasedRealm`. It supports setting a `PermissionsForRoleProvider`. Additionally, a `PermissionsForRoleProvider` can be used with `com.sap.sse.security.ui.shared.UserDTO.getAllPermissions(...)` so that UI clients can have the same set of permissions inferred for a user that the Shiro back-end will use for permission checks, so as to enable an disable UI features based on the user's permissions. ## How to Configure +## Notes on Replication + From bd327fe876c63bf0b049b81d74c5879ad6e2e360 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 12:35:59 +0100 Subject: [PATCH 202/264] added comments and cleaned up structure in com.sap.sse.security/resources/shiro.ini to make it a good copy template, too --- java/com.sap.sse.security/resources/shiro.ini | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index ae62d9f5c5d..b06e5ca4220 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -1,36 +1,53 @@ [main] -# Use Built-in Cache Manager credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher - -# base64 encoding, not hex in this example: credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 +# Realm configuration: upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher oauthRealm = com.sap.sse.security.OAuthRealm bearerTokenRealm = com.sap.sse.security.BearerTokenRealm - securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm +# Putting it all together: sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager +sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO +securityManager.sessionManager.sessionDAO = $sessionDAO +cacheManager = com.sap.sse.security.SessionCacheManager +securityManager.cacheManager = $cacheManager +# Authentication Filter Configurations +# ------------------------------------ + +# 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 -sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO -securityManager.sessionManager.sessionDAO = $sessionDAO - -cacheManager = com.sap.sse.security.SessionCacheManager -securityManager.cacheManager = $cacheManager +# Specifying filter chains for URL patterns [urls] /api/restsecurity/login = authc From bbda88f66616770991c34d288f527d3457703b8d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 11:42:36 +0000 Subject: [PATCH 203/264] Updated security (markdown) --- wiki/security.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/wiki/security.md b/wiki/security.md index 9e4c3777d11..1fcb756f4ea 100644 --- a/wiki/security.md +++ b/wiki/security.md @@ -16,5 +16,36 @@ The inference from roles to the permissions implied by those roles happens by im ## How to Configure +Shiro security is largely configured by `shiro.ini` files in OSGi Web Bundlesand their `WEB-INF/web.xml` descriptors. Shiro web security hinges on the use of servlet filters that are configured in `web.xml`. The corresponding section to enable Shiro security for a Web Bundle looks like this: + + + shiroEnvironmentClass + org.apache.shiro.web.env.IniWebEnvironment + + + org.apache.shiro.web.env.EnvironmentLoaderListener + + + ShiroFilter + org.apache.shiro.web.servlet.ShiroFilter + + + + ShiroFilter + /* + REQUEST + FORWARD + INCLUDE + ERROR + + +For this to work, the Web Bundle requires at least the two bundles `org.apache.shiro.core` and `org.apache.shiro.web` which are provided by the target platform. Furthermore, the bundle should require `com.sap.sse.security` so as to get support for the common user store, session replication support and the common roles and permissions management. + +The Web Bundle then provides a `shiro.ini` file in its classpath root, e.g., directly within its `src` or `resources` source folder. The `shiro.ini` file contains essential configuration information about which realms, which session and which cache manager to use. It also configures URLs for login pages, default success pages and permissions required for access to URLs. The file `com.sap.sse.security/resources/shiro.ini` serves as a reasonable copy template. In the `[urls]` section the `shiro.ini` flie provides so-called filter chains for specific or pattern-based sets of URLs. In particular, the configuration can require the authenticated user to have specific roles and / or specific permissions to access the URL. Note the use of the `AnyOfRolesFilter` and how it is different from the regular `roles` filter. + +## How to Implement Permission Checks + ## Notes on Replication From 1cc22d38585ddafb2ab73fb163f6b191d9a890af Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 12:42:47 +0100 Subject: [PATCH 204/264] added anyofroles filter declaration to default shiro.ini --- java/com.sap.sse.security/resources/shiro.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index b06e5ca4220..f17c4d47ee0 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -22,6 +22,11 @@ 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 From 83b2e20dab47c297dcc30f772b8f6d282ecd2def Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 11:48:47 +0000 Subject: [PATCH 205/264] Updated security (markdown) --- wiki/security.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wiki/security.md b/wiki/security.md index 1fcb756f4ea..0181b4d8794 100644 --- a/wiki/security.md +++ b/wiki/security.md @@ -47,5 +47,12 @@ The Web Bundle then provides a `shiro.ini` file in its classpath root, e.g., dir ## How to Implement Permission Checks +There are generally two ways in which some feature can require the user to be equipped with permissions: declaratively in the `shiro.ini` file's `[urls]` section; or programmatically by using something like ``org.apache.shiro.SecurityUtils.getSubject().checkPermission(...)`` which will throw an `AuthorizationException` in case the user lacks the necessary permissions. + +Example for a declarative permission check: + [urls] + /api/v1/events = bearerToken, perms["event:view"] +This requires users trying to access the URL `/api/v1/events` to be authenticated using a valid `JSESSIONID` cookie or any authentication supported by the `bearerToken` filter such that the authenticated user has permissions that imply the `event:view:*` permission. + ## Notes on Replication From 62ddf964140e4e360dd716e8db0e28d0bb1d9776 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 12:21:57 +0000 Subject: [PATCH 206/264] Updated security (markdown) --- wiki/security.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/wiki/security.md b/wiki/security.md index 0181b4d8794..6297a9b073b 100644 --- a/wiki/security.md +++ b/wiki/security.md @@ -54,5 +54,17 @@ Example for a declarative permission check: /api/v1/events = bearerToken, perms["event:view"] This requires users trying to access the URL `/api/v1/events` to be authenticated using a valid `JSESSIONID` cookie or any authentication supported by the `bearerToken` filter such that the authenticated user has permissions that imply the `event:view:*` permission. +Example for a programmatic check: + SecurityUtils.getSubject().checkPermission("event:view"); + +## Standard REST Security Services + ## Notes on Replication +The `SecurityService` implementation is a `Replicable` that is replicated from a master to its replicas and in case of replica-initiated operations also the other way. Also, the `SecurityService` is registered with the OSGi service registry and can be discovered by other components. It has a `UserStore` and a cache manager (`com.sap.sse.security.SessionCacheManager`) that is replication aware. This cache manager has to be configured in the `shiro.ini` file. + +Whenever a cache entry is updated (particularly the details of a user session such as creating a new session, expiring a session or touching a session for timeout refresh), the effect is replicated. For performance reasons, a special rule is in place for the `touch` operation (see `SecurityWebSessionManager.touch(...)`). Instead of replicating this effect immediately, a timer is launched which considers the session timeout and the assumed time it takes to replicate an operation and collects and delays such touch operations and sends them at the latest possible time point to ensure a session that got touched doesn't expire anywhere. + +All operations affecting the `UserStore` are also replicated by the `SecurityService`, in particular the creation, update and removal of users, their roles and their permissions. + +It is planned to enable replicating the `SecurityService` from a different master server than the one used for replicating the application domain data. See also [Bug 2465](http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2465). This would allow an administrator to set up a separate user management server that works as a central "directory" for several other archive and event servers, sharing user management data across such a landscape. \ No newline at end of file From 071bbd276468926b1c6c1adf0f012fa6cb16e65c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 12:25:44 +0000 Subject: [PATCH 207/264] Updated security (markdown) --- wiki/security.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wiki/security.md b/wiki/security.md index 6297a9b073b..e1730f3ef38 100644 --- a/wiki/security.md +++ b/wiki/security.md @@ -59,6 +59,8 @@ Example for a programmatic check: ## Standard REST Security Services +There are a number of RESTlets registered under the `/security` context root that allow RESTful clients to log in and log out a user, as well as obtain a bearer access token for a user which can then be used in conjunction with the `bearerToken` / `BearerTokenOrBasicOrFormAuthenticationFilter` authentication filter. These services are described in more detail at [/security/webservices/api/index.html](http://sapsailing.com/security/webservices/api/index.html). + ## Notes on Replication The `SecurityService` implementation is a `Replicable` that is replicated from a master to its replicas and in case of replica-initiated operations also the other way. Also, the `SecurityService` is registered with the OSGi service registry and can be discovered by other components. It has a `UserStore` and a cache manager (`com.sap.sse.security.SessionCacheManager`) that is replication aware. This cache manager has to be configured in the `shiro.ini` file. From 6bd56de7f10dcc01cbccbed54bcf620d0f8b7d7d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 13:48:51 +0100 Subject: [PATCH 208/264] cleaned up shiro.ini stuff further, added more comments --- .../src/main/resources/shiro.ini | 59 ++++++++++--------- java/com.sap.sse.security/resources/shiro.ini | 11 +++- .../security/impl/SecurityServiceImpl.java | 2 +- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini index d4dfdff48d2..2e902cfaee4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini @@ -1,17 +1,10 @@ [main] -shiro.loginUrl = /security/ui/Login.html -shiro.successUrl = /UserManagement.html -# Use Built-in Chache Manager - -anyofroles = com.sap.sse.security.AnyOfRolesFilter -anyofroles.loginUrl = ../security/ui/Login.html - credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher -# base64 encoding, not hex in this example: credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 +# Realm configuration: permissionsForRoleProvider = com.sap.sailing.gwt.ui.client.shared.security.SailingPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher @@ -21,34 +14,42 @@ oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider bearerTokenRealm = com.sap.sse.security.BearerTokenRealm bearerTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider -securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm - +# Putting it all together: sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager -#globalCookie = com.sap.sse.security.GlobalCookie -#securityManager.sessionManager.sessionIdCookie = $globalCookie - -authc = com.sap.sse.security.CustomFilter -authc.loginUrl = ../security/ui/Login.html?app=Sailing%20Analytics -authc.successUrl = /security/ui/UserManagement.html - -roles.loginUrl = ../security/ui/Login.html - sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO securityManager.sessionManager.sessionDAO = $sessionDAO - cacheManager = com.sap.sse.security.SessionCacheManager securityManager.cacheManager = $cacheManager -#securityManager.sessionMode = native +# Authentication Filter Configurations +# ------------------------------------ + +# Set default login and success URLs for all filters: +shiro.loginUrl = ../security/ui/Login.html?app=Sailing%20Analytics +shiro.successUrl = ../security/ui/UserManagement.html + +# 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 + +# 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 [urls] -# The /security/ui/Login.html is not restricted to authenticated users (otherwise no one could log in!), but -# the 'authc' filter must still be specified for it so it can process that url's -# login submissions. It is 'smart' enough to allow those requests through as specified by the -# shiro.loginUrl above. - -/security/ui/UserManagement.html = authc,roles[admin] -/LeaderboardEditing.html = authc,anyofroles[admin,eventmanager] +/LeaderboardEditing.html = customGwt,anyofroles[admin,eventmanager] # The admin console displays its features based on the user's roles -/AdminConsole.html = authc +/AdminConsole.html = customGwt diff --git a/java/com.sap.sse.security/resources/shiro.ini b/java/com.sap.sse.security/resources/shiro.ini index f17c4d47ee0..3519320d74b 100644 --- a/java/com.sap.sse.security/resources/shiro.ini +++ b/java/com.sap.sse.security/resources/shiro.ini @@ -5,11 +5,20 @@ credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 # Realm configuration: +# -------------------- + +# Uncomment the following line to specify a bundle-specific permissions-for-role provider +# permissionsForRoleProvider = com.sap.myapp....MyAppPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher +# Uncomment the following line make the upRealm aware of the permissionsForRoleProvider +# upRealm.permissionsForRoleProvider = $permissionsForRoleProvider oauthRealm = com.sap.sse.security.OAuthRealm +# Uncomment the following line make the oauthRealm aware of the permissionsForRoleProvider +# oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider bearerTokenRealm = com.sap.sse.security.BearerTokenRealm -securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm +# Uncomment the following line make the bearerTokenRealm aware of the permissionsForRoleProvider +# bearerTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider # Putting it all together: sessionManager = com.sap.sse.security.SecurityWebSessionManager diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index 908108984d3..be86335c4c1 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -864,7 +864,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica } @Override - public CacheManager getCacheManager() { + public ReplicatingCacheManager getCacheManager() { return cacheManager; } From 20c2d8acef5443ea513122b22d5a90dfbd1a4803 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 13:54:07 +0100 Subject: [PATCH 209/264] harmonized the other shiro.ini files; requiring permission manage_users for UserManagement.html --- .../resources/shiro.ini | 47 +++++++++++---- .../src/main/resources/shiro.ini | 59 +++++++++++++++---- 2 files changed, 86 insertions(+), 20 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/resources/shiro.ini b/java/com.sap.sailing.server.gateway/resources/shiro.ini index 10e91bb81bd..53d24b13858 100755 --- a/java/com.sap.sailing.server.gateway/resources/shiro.ini +++ b/java/com.sap.sailing.server.gateway/resources/shiro.ini @@ -1,36 +1,63 @@ [main] -# Use Built-in Cache Manager credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher - -# base64 encoding, not hex in this example: credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 +# Realm configuration: +# -------------------- + +permissionsForRoleProvider = com.sap.sailing.gwt.ui.client.shared.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 -securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm - +# Putting it all together: sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager +sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO +securityManager.sessionManager.sessionDAO = $sessionDAO +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 -sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO -securityManager.sessionManager.sessionDAO = $sessionDAO - -cacheManager = com.sap.sse.security.SessionCacheManager -securityManager.cacheManager = $cacheManager +# Specifying filter chains for URL patterns [urls] # Uncomment the following to require the permission "event:view" in order to use /api/v1/events: diff --git a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini index df6c11f0518..41d41da7220 100755 --- a/java/com.sap.sse.security.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sse.security.ui/src/main/resources/shiro.ini @@ -1,30 +1,69 @@ + [main] credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher -# base64 encoding, not hex in this example: credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 +# Realm configuration: +# -------------------- + +# Uncomment the following line to specify a bundle-specific permissions-for-role provider +# permissionsForRoleProvider = com.sap.myapp....MyAppPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher +# Uncomment the following line make the upRealm aware of the permissionsForRoleProvider +# upRealm.permissionsForRoleProvider = $permissionsForRoleProvider oauthRealm = com.sap.sse.security.OAuthRealm +# Uncomment the following line make the oauthRealm aware of the permissionsForRoleProvider +# oauthRealm.permissionsForRoleProvider = $permissionsForRoleProvider bearerTokenRealm = com.sap.sse.security.BearerTokenRealm +# Uncomment the following line make the bearerTokenRealm aware of the permissionsForRoleProvider +# bearerTokenRealm.permissionsForRoleProvider = $permissionsForRoleProvider -securityManager.realms = $upRealm, $oauthRealm, $bearerTokenRealm - +# Putting it all together: sessionManager = com.sap.sse.security.SecurityWebSessionManager securityManager.sessionManager = $sessionManager - -authc = com.sap.sse.security.CustomFilter -authc.loginUrl = /security/ui/Login.html -authc.successUrl = /UserManagement.html - sessionDAO = org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO securityManager.sessionManager.sessionDAO = $sessionDAO - 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] /Login.html = anon -/UserManagement.html = authc,roles[admin] +/UserManagement.html = customGwt,perms[manage_users] /EditProfile.html = anon From 4fb9c3b1c0b88f46386f29a5c02392f6b5651dd1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 13:42:20 +0000 Subject: [PATCH 210/264] Updated security (markdown) --- wiki/security.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/wiki/security.md b/wiki/security.md index e1730f3ef38..09bc53d89bf 100644 --- a/wiki/security.md +++ b/wiki/security.md @@ -45,6 +45,13 @@ For this to work, the Web Bundle requires at least the two bundles `org.apache.s The Web Bundle then provides a `shiro.ini` file in its classpath root, e.g., directly within its `src` or `resources` source folder. The `shiro.ini` file contains essential configuration information about which realms, which session and which cache manager to use. It also configures URLs for login pages, default success pages and permissions required for access to URLs. The file `com.sap.sse.security/resources/shiro.ini` serves as a reasonable copy template. In the `[urls]` section the `shiro.ini` flie provides so-called filter chains for specific or pattern-based sets of URLs. In particular, the configuration can require the authenticated user to have specific roles and / or specific permissions to access the URL. Note the use of the `AnyOfRolesFilter` and how it is different from the regular `roles` filter. +All authentication filters inheriting from `com.sap.sse.security.AbstractUserStoreBasedRealm` can provide an instance for the `permissionsForRoleProvider` property in the `shiro.ini` configuration file, e.g., as follows: + + permissionsForRoleProvider = com.sap.sailing.gwt.ui.client.shared.security.SailingPermissionsForRoleProvider + upRealm = com.sap.sse.security.UsernamePasswordRealm + upRealm.credentialsMatcher = $credentialsMatcher + upRealm.permissionsForRoleProvider = $permissionsForRoleProvider + ## How to Implement Permission Checks There are generally two ways in which some feature can require the user to be equipped with permissions: declaratively in the `shiro.ini` file's `[urls]` section; or programmatically by using something like ``org.apache.shiro.SecurityUtils.getSubject().checkPermission(...)`` which will throw an `AuthorizationException` in case the user lacks the necessary permissions. @@ -57,6 +64,14 @@ This requires users trying to access the URL `/api/v1/events` to be authenticate Example for a programmatic check: SecurityUtils.getSubject().checkPermission("event:view"); +### Special Case: Permission Checks in the AdminConsole + +The `AdminConsolePanel` provides some generic support for permission handling, aiming at customizing the UI to the user's actual permissions. Ideally, a user would only see UI features he/she has the permission to use. In particular, we don't want to bother the user by showing panels he/she isn't permitted to use at all. + +When an administration entry point uses the `AdminConsolePanel`, each widget shown in the panel can be configured to require any of a set of permissions. For example, a panel for managing users would ask for permission `manage_users`. This is actually short for `manage_users:*:*`, a wildcard permission that implies all more specific permissions such as `manage_users:view:*` or `manage_users:edit:peter`. However, a specific permissions such as `manage_users:edit:peter` doesn't imply `manage_users:*:*` by the rules of wildcard permissions. + +The widget shall be shown as soon as the user has any permission for managing users, even only a specific one. It is then up to the implementation of the widget to further constrain user interaction and information displayed, based on the user's actual permissions. To implement this, the permission check for the appearance of administration console widgets is slightly modified: a widget will appear if the permission it requires implies any of the permissions the user has, or if any of the user's permissions implies the permission required by the widget. This way, a user having a specific permission such as `manage_users:edit:peter` will see a widget that requires `manage_users`. Also, a user having the administrator permission `*` will see the same widget because `*` implies all other permissions, particularly `manage_users`. + ## Standard REST Security Services There are a number of RESTlets registered under the `/security` context root that allow RESTful clients to log in and log out a user, as well as obtain a bearer access token for a user which can then be used in conjunction with the `bearerToken` / `BearerTokenOrBasicOrFormAuthenticationFilter` authentication filter. These services are described in more detail at [/security/webservices/api/index.html](http://sapsailing.com/security/webservices/api/index.html). From 3fa30e7f1e1b30e99e9e60ca3f58d2846807fbb1 Mon Sep 17 00:00:00 2001 From: Lennart Hensler Date: Thu, 26 Feb 2015 15:21:29 +0100 Subject: [PATCH 211/264] Introduced the HasMarkPassingContext as data type --- .../Sailing_StringMessages_de.properties | 9 ++- .../Sailing_StringMessages_en.properties | 9 ++- .../com/sap/sailing/datamining/Activator.java | 4 +- .../SailingDataRetrieverChainDefinitions.java | 45 ++++++----- .../datamining/data/HasGPSFixContext.java | 4 +- .../data/HasMarkPassingContext.java | 29 ++++++++ .../MarkPassingRetrievalProcessor.java | 47 ++++++++++++ .../impl/data/MarkPassingWithContext.java | 74 +++++++++++++++++++ .../sap/sailing/domain/base/Nationality.java | 2 +- .../sailing/domain/base/WithNationality.java | 2 +- .../shared/impl/GenericGroupKey.java | 2 +- .../DataRetrieverChainDefinition.java | 10 +-- .../DataRetrieverTypeWithInformation.java | 6 +- .../SimpleDataRetrieverChainDefinition.java | 12 +-- ...dDoubleDataMedianAggregationProcessor.java | 4 +- 15 files changed, 209 insertions(+), 50 deletions(-) create mode 100644 java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java create mode 100644 java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java create mode 100644 java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties index ff7916e9925..370f6b81434 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties @@ -3,7 +3,8 @@ DistanceTraveled=zur Boat=Boot Team=Team Name=Name -NationalityAcronym=Nationalität +Nationality=Nationalitäts +Acronym=Abkürzung SailID=Segelnummer LegNumber=Nummer des Schenkels LegType=Art des Schenkels @@ -13,13 +14,17 @@ CourseArea=Regattabahn BoatClass=Bootsklasse Fleet=Flotte Race=Rennen -SailingDomainRetrieverChain=Segel-Domänen Datenbeschaffer +LegSailingDomainRetrieverChain=Segel-Domänen Leg-Beschaffer +GPSFixSailingDomainRetrieverChain=Segel-Domänen GPS-Fix-Beschaffer +MarkPassingSailingDomainRetrieverChain=Segel-Domänen Tonnenrundungs-Beschaffer LeaderboardGroup=Ranglisten Gruppe RegattaLeaderboard=Regatta Rangliste Leg=Schenkel +MarkPassing=Tonnenrundung LegOfCompetitor=Schenkel eines Teilnehmers GpsFix=GPS-Fix Speed=Geschwindigkeit +SpeedTenSecondsBefore=Geschwindigkeit 10s vorher InKnots=in Knoten WindStrengthInBeaufort=Windstärke in Beaufort BftClusterGroup=Windstärke in Beaufort diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties index f7bc61b131e..034d610a9aa 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties @@ -1,4 +1,5 @@ Speed=Speed +SpeedTenSecondsBefore=Speed 10s before InKnots=in knots DistanceTraveled=Distance Traveled Name=Name @@ -6,7 +7,8 @@ LegType=Leg Type SailID=Sail Number Year=Year LegNumber=Leg Number -NationalityAcronym=Nationality +Nationality=Nationality +Acronym=Acronym Regatta=Regatta CourseArea=Course Area BoatClass=Boat Class @@ -15,10 +17,13 @@ Race=Race Competitor=Competitor Boat=Boat Team=Team -SailingDomainRetrieverChain=Sailing Domain Retriever +LegSailingDomainRetrieverChain=Sailing Domain Leg Retriever +GPSFixSailingDomainRetrieverChain=Sailing Domain GPS Fix Retriever +MarkPassingSailingDomainRetrieverChain=Sailing Domain Mark Passing Retriever LeaderboardGroup=Leaderboard Group RegattaLeaderboard=Regatta Leaderboard Leg=Leg +MarkPassing=Mark Passing LegOfCompetitor=Leg of competitor GpsFix=GPS-Fix WindStrengthInBeaufort=Wind Strength in Beaufort diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java index 08c95073587..f1acd00bf23 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java @@ -9,6 +9,7 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasMarkPassingContext; import com.sap.sailing.datamining.data.HasTrackedLegContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; @@ -31,7 +32,7 @@ public class Activator implements BundleActivator, DataMiningBundleService { public Activator() { dataRetrieverChainDefinitions = new SailingDataRetrieverChainDefinitions(); - sailingServerStringMessages = new ResourceBundleStringMessagesImpl(STRING_MESSAGES_BASE_NAME, Activator.class.getClassLoader()); + sailingServerStringMessages = new ResourceBundleStringMessagesImpl(STRING_MESSAGES_BASE_NAME, getClass().getClassLoader()); clusterGroups = new SailingClusterGroups(); } @@ -59,6 +60,7 @@ public class Activator implements BundleActivator, DataMiningBundleService { internalClasses.add(HasTrackedLegContext.class); internalClasses.add(HasTrackedLegOfCompetitorContext.class); internalClasses.add(HasGPSFixContext.class); + internalClasses.add(HasMarkPassingContext.class); return internalClasses; } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java index 478ac9a25ad..a1a6cc23517 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrieverChainDefinitions.java @@ -4,20 +4,21 @@ import java.util.ArrayList; import java.util.Collection; import com.sap.sailing.datamining.data.HasGPSFixContext; +import com.sap.sailing.datamining.data.HasMarkPassingContext; import com.sap.sailing.datamining.data.HasTrackedLegContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor; import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.MarkPassingRetrievalProcessor; import com.sap.sailing.datamining.impl.components.RegattaLeaderboardRetrievalProcessor; -import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor; import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor; import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.server.RacingEventService; import com.sap.sse.datamining.DataRetrieverChainDefinition; -import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.SimpleDataRetrieverChainDefinition; public class SailingDataRetrieverChainDefinitions { @@ -27,33 +28,29 @@ public class SailingDataRetrieverChainDefinitions { public SailingDataRetrieverChainDefinitions() { dataRetrieverChainDefinitions = new ArrayList<>(); - DataRetrieverChainDefinition legOfCompetitorRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( - RacingEventService.class, HasTrackedLegOfCompetitorContext.class, "SailingDomainRetrieverChain"); - @SuppressWarnings("unchecked") - Class> leaderboardGroupRetrieverType = (Class>) (Class) LeaderboardGroupRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.startWith(leaderboardGroupRetrieverType, LeaderboardGroup.class, "LeaderboardGroup"); - @SuppressWarnings("unchecked") - Class> regattaLeaderboardRetrieverType = (Class>) (Class) RegattaLeaderboardRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.addAfter(leaderboardGroupRetrieverType, regattaLeaderboardRetrieverType, + final DataRetrieverChainDefinition trackedRaceRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + RacingEventService.class, HasTrackedLegOfCompetitorContext.class, "RaceSailingDomainRetrieverChain"); + trackedRaceRetrieverChainDefinition.startWith(LeaderboardGroupRetrievalProcessor.class, LeaderboardGroup.class, "LeaderboardGroup"); + trackedRaceRetrieverChainDefinition.addAfter(LeaderboardGroupRetrievalProcessor.class, RegattaLeaderboardRetrievalProcessor.class, RegattaLeaderboard.class, "RegattaLeaderboard"); - @SuppressWarnings("unchecked") - Class> raceRetrieverType = (Class>) (Class) TrackedRaceRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.addAfter(regattaLeaderboardRetrieverType, raceRetrieverType, + trackedRaceRetrieverChainDefinition.addAfter(RegattaLeaderboardRetrievalProcessor.class, TrackedRaceRetrievalProcessor.class, HasTrackedRaceContext.class, "Race"); - @SuppressWarnings("unchecked") - Class> legRetrieverType = (Class>) (Class) TrackedLegRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.addAfter(raceRetrieverType, legRetrieverType, HasTrackedLegContext.class, "Leg"); - @SuppressWarnings("unchecked") - Class> legOfCompetitorRetrieverType = (Class>) (Class) TrackedLegOfCompetitorRetrievalProcessor.class; - legOfCompetitorRetrieverChainDefinition.endWith(legRetrieverType, legOfCompetitorRetrieverType, + + final DataRetrieverChainDefinition markPassingRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + trackedRaceRetrieverChainDefinition, HasMarkPassingContext.class, "MarkPassingSailingDomainRetrieverChain"); + markPassingRetrieverChainDefinition.endWith(TrackedRaceRetrievalProcessor.class, MarkPassingRetrievalProcessor.class, HasMarkPassingContext.class, "MarkPassing"); + dataRetrieverChainDefinitions.add(markPassingRetrieverChainDefinition); + + final DataRetrieverChainDefinition legOfCompetitorRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + trackedRaceRetrieverChainDefinition, HasTrackedLegOfCompetitorContext.class, "LegSailingDomainRetrieverChain"); + legOfCompetitorRetrieverChainDefinition.addAfter(TrackedRaceRetrievalProcessor.class, TrackedLegRetrievalProcessor.class, HasTrackedLegContext.class, "Leg"); + legOfCompetitorRetrieverChainDefinition.endWith(TrackedLegRetrievalProcessor.class, TrackedLegOfCompetitorRetrievalProcessor.class, HasTrackedLegOfCompetitorContext.class, "LegOfCompetitor"); dataRetrieverChainDefinitions.add(legOfCompetitorRetrieverChainDefinition); - DataRetrieverChainDefinition gpsFixRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( - legOfCompetitorRetrieverChainDefinition, HasGPSFixContext.class, "SailingDomainRetrieverChain"); - @SuppressWarnings("unchecked") - Class> gpsFixRetrieverType = (Class>) (Class) GPSFixRetrievalProcessor.class; - gpsFixRetrieverChainDefinition.endWith(legOfCompetitorRetrieverType, gpsFixRetrieverType, + final DataRetrieverChainDefinition gpsFixRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + legOfCompetitorRetrieverChainDefinition, HasGPSFixContext.class, "GPSFixSailingDomainRetrieverChain"); + gpsFixRetrieverChainDefinition.endWith(TrackedLegOfCompetitorRetrievalProcessor.class, GPSFixRetrievalProcessor.class, HasGPSFixContext.class, "GpsFix"); dataRetrieverChainDefinitions.add(gpsFixRetrieverChainDefinition); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java index 1f8db410e99..0227119e98f 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasGPSFixContext.java @@ -13,10 +13,10 @@ public interface HasGPSFixContext { @Connector public HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext(); - @Connector + @Connector(ordinal=1) public GPSFixMoving getGPSFix(); - @Dimension(messageKey="WindStrengthInBeaufort", ordinal=10) + @Dimension(messageKey="WindStrengthInBeaufort", ordinal=11) public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages); } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java new file mode 100644 index 00000000000..738e8a28575 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java @@ -0,0 +1,29 @@ +package com.sap.sailing.datamining.data; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sse.datamining.shared.annotations.Connector; +import com.sap.sse.datamining.shared.annotations.Dimension; + +public interface HasMarkPassingContext { + + @Connector + HasTrackedRaceContext getTrackedRaceContext(); + + @Connector + MarkPassing getMarkPassing(); + + @Connector(messageKey="Competitor") + Competitor getCompetitor(); + + @Connector(messageKey="Speed", ordinal=1) + Speed getSpeed(); + + @Connector(messageKey="SpeedTenSecondsBefore", ordinal=2) + Speed getSpeedTenSecondsBefore(); + + @Dimension(messageKey="LegType", ordinal=6) + String getPreviousLegTypeSignifier() throws NoWindException; +} \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java new file mode 100644 index 00000000000..4dec85496f6 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java @@ -0,0 +1,47 @@ +package com.sap.sailing.datamining.impl.components; + +import java.util.Collection; +import java.util.NavigableSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; + +import java.util.HashSet; +import com.sap.sailing.datamining.data.HasMarkPassingContext; +import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.impl.data.MarkPassingWithContext; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor; + +public class MarkPassingRetrievalProcessor extends + AbstractSimpleRetrievalProcessor { + + public MarkPassingRetrievalProcessor(ExecutorService executor, + Collection> resultReceivers) { + super(HasTrackedRaceContext.class, HasMarkPassingContext.class, executor, resultReceivers); + } + + @Override + protected Iterable retrieveData(HasTrackedRaceContext element) { + TrackedRace trackedRace = element.getTrackedRace(); + Set result = new HashSet<>(); + trackedRace.getRace().getCourse().lockForRead(); + try { + for (Competitor competitor : trackedRace.getRace().getCompetitors()) { + NavigableSet markPassingsForCompetitor = trackedRace.getMarkPassings(competitor); + trackedRace.lockForRead(markPassingsForCompetitor); + try { + markPassingsForCompetitor.stream().forEach(mp->result.add(new MarkPassingWithContext(element, mp))); + } finally { + trackedRace.unlockAfterRead(markPassingsForCompetitor); + } + } + } finally { + trackedRace.getRace().getCourse().unlockAfterRead(); + } + return result; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java new file mode 100644 index 00000000000..45fb928efee --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java @@ -0,0 +1,74 @@ +package com.sap.sailing.datamining.impl.data; + +import com.sap.sailing.datamining.data.HasMarkPassingContext; +import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedRace; + +public class MarkPassingWithContext implements HasMarkPassingContext { + private final HasTrackedRaceContext trackedRaceContext; + private final MarkPassing markPassing; + + public MarkPassingWithContext(HasTrackedRaceContext trackedRaceContext, MarkPassing markPassing) { + super(); + this.trackedRaceContext = trackedRaceContext; + this.markPassing = markPassing; + } + + @Override + public HasTrackedRaceContext getTrackedRaceContext() { + return trackedRaceContext; + } + + @Override + public MarkPassing getMarkPassing() { + return markPassing; + } + + @Override + public Competitor getCompetitor() { + return markPassing.getCompetitor(); + } + + @Override + public Speed getSpeed() { + return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(getMarkPassing().getTimePoint()); + } + + @Override + public Speed getSpeedTenSecondsBefore() { + return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(getMarkPassing().getTimePoint().minus(10000 /* ms */)); + } + + private TrackedRace getTrackedRace() { + return trackedRaceContext.getTrackedRace(); + } + + @Override + public String getPreviousLegTypeSignifier() throws NoWindException { + final String result; + TrackedLeg previousLeg = getPreviousLeg(); + if (previousLeg == null) { + result = "Start"; + } else { + result = previousLeg.getLegType(getMarkPassing().getTimePoint()).toString(); + } + return result; + } + + private TrackedLeg getPreviousLeg() { + final TrackedLeg result; + Waypoint waypoint = markPassing.getWaypoint(); + if (waypoint == getTrackedRace().getRace().getCourse().getFirstWaypoint()) { + result = null; + } else { + result = getTrackedRace().getTrackedLegFinishingAt(waypoint); + } + return result; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Nationality.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Nationality.java index 2f44763054e..d675e7a25a1 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Nationality.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Nationality.java @@ -6,7 +6,7 @@ import com.sap.sse.common.Named; import com.sap.sse.datamining.shared.annotations.Dimension; public interface Nationality extends Named, IsManagedByCache { - @Dimension(messageKey="NationalityAcronym") + @Dimension(messageKey="Acronym") String getThreeLetterIOCAcronym(); CountryCode getCountryCode(); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java index c145cdb6e59..33234b9a7c0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/WithNationality.java @@ -3,6 +3,6 @@ package com.sap.sailing.domain.base; import com.sap.sse.datamining.shared.annotations.Connector; public interface WithNationality { - @Connector(ordinal=7) + @Connector(messageKey="Nationality", ordinal=8) Nationality getNationality(); } diff --git a/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/GenericGroupKey.java b/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/GenericGroupKey.java index 6c04ba3ddb6..305bc07cd25 100644 --- a/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/GenericGroupKey.java +++ b/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/GenericGroupKey.java @@ -39,7 +39,7 @@ public class GenericGroupKey extends AbstractGroupKey { @Override public String asString() { - return value.toString(); + return value == null ? "null" : value.toString(); } @Override diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataRetrieverChainDefinition.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataRetrieverChainDefinition.java index 1d3927e943c..889c4e6a585 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataRetrieverChainDefinition.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/DataRetrieverChainDefinition.java @@ -19,16 +19,16 @@ public interface DataRetrieverChainDefinition { public String getLocalizedName(Locale locale, ResourceBundleStringMessages stringMessages); - public void startWith(Class> retrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey); + public void startWith(Class> retrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey); public void - addAfter(Class> previousRetrieverType, - Class> nextRetrieverType, + addAfter(Class> previousRetrieverType, + Class> nextRetrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey); public void - endWith(Class> previousRetrieverType, - Class> lastRetrieverType, + endWith(Class> previousRetrieverType, + Class> lastRetrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey); public List> getDataRetrieverTypesWithInformation(); diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataRetrieverTypeWithInformation.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataRetrieverTypeWithInformation.java index 75d94390afd..a6ec6485aa7 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataRetrieverTypeWithInformation.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/DataRetrieverTypeWithInformation.java @@ -4,11 +4,11 @@ import com.sap.sse.datamining.components.Processor; public class DataRetrieverTypeWithInformation { - private final Class> retrieverType; + private final Class> retrieverType; private final Class retrievedDataType; private final String retrievedDataTypeMessageKey; - public DataRetrieverTypeWithInformation(Class> retrieverType, + public DataRetrieverTypeWithInformation(Class> retrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey) { this.retrieverType = retrieverType; @@ -16,7 +16,7 @@ public class DataRetrieverTypeWithInformation { this.retrievedDataTypeMessageKey = retrievedDataTypeMessageKey; } - public Class> getRetrieverType() { + public Class> getRetrieverType() { return retrieverType; } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/SimpleDataRetrieverChainDefinition.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/SimpleDataRetrieverChainDefinition.java index eea1a097d68..5d0601d37b6 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/SimpleDataRetrieverChainDefinition.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/SimpleDataRetrieverChainDefinition.java @@ -61,7 +61,7 @@ public class SimpleDataRetrieverChainDefinition implem } @Override - public void startWith(Class> retrieverType, + public void startWith(Class> retrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey) { if (isInitialized()) { throw new UnsupportedOperationException("This retriever chain definition already has been started with '" @@ -79,8 +79,8 @@ public class SimpleDataRetrieverChainDefinition implem @Override public void addAfter( - Class> previousRetrieverType, - Class> nextRetrieverType, + Class> previousRetrieverType, + Class> nextRetrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey) { if (!isInitialized()) { throw new UnsupportedOperationException("This retriever chain definition hasn't been started yet"); @@ -103,7 +103,7 @@ public class SimpleDataRetrieverChainDefinition implem } private void checkThatRetrieverHasUsableConstructor( - Class> retrieverType) { + Class> retrieverType) { try { retrieverType.getConstructor(ExecutorService.class, Collection.class); } catch (NoSuchMethodException | SecurityException e) { @@ -114,8 +114,8 @@ public class SimpleDataRetrieverChainDefinition implem @Override public void endWith( - Class> previousRetrieverType, - Class> lastRetrieverType, Class retrievedDataType, + Class> previousRetrieverType, + Class> lastRetrieverType, Class retrievedDataType, String retrievedDataTypeMessageKey) { addAfter(previousRetrieverType, lastRetrieverType, retrievedDataType, retrievedDataTypeMessageKey); isComplete = true; diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDoubleDataMedianAggregationProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDoubleDataMedianAggregationProcessor.java index f9eced04894..49c2c7a32a3 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDoubleDataMedianAggregationProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDoubleDataMedianAggregationProcessor.java @@ -48,11 +48,11 @@ public class ParallelGroupedDoubleDataMedianAggregationProcessor private Double getMedianOf(List values) { Collections.sort(values); if (listSizeIsEven(values)) { - int index1 = values.size() / 2; + int index1 = (values.size() / 2) - 1; int index2 = index1 + 1; return (values.get(index1) + values.get(index2)) / 2; } else { - int index = (values.size() + 1) / 2; + int index = ((values.size() + 1) / 2) - 1; return values.get(index); } } From 0a40e964236b5247c49ced2690455b9683df1a62 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Thu, 26 Feb 2015 15:57:18 +0100 Subject: [PATCH 212/264] Introduced settings for controlling the visibility of sail id and name column of a leaderboard --- .../client/place/regatta/RegattaActivity.java | 16 ++++++++++++- .../client/place/series/SeriesActivity.java | 16 ++++++++++++- .../sailing/gwt/ui/client/StringMessages.java | 2 ++ .../gwt/ui/client/StringMessages.properties | 4 +++- .../ui/client/StringMessages_de.properties | 4 +++- .../ui/client/StringMessages_ru.properties | 4 +++- .../ui/leaderboard/LeaderboardEntryPoint.java | 19 +++++++++++++-- .../gwt/ui/leaderboard/LeaderboardPanel.java | 24 ++++++++++++++++++- .../ui/leaderboard/LeaderboardSettings.java | 16 ++++++++++++- .../LeaderboardSettingsDialogComponent.java | 16 ++++++++++++- .../LeaderboardSettingsFactory.java | 10 +++++--- .../LeaderboardUrlConfigurationDialog.java | 3 ++- .../leaderboard/LeaderboardUrlSettings.java | 10 ++++++++ 13 files changed, 130 insertions(+), 14 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java index 685be39e328..850f49d680b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java @@ -153,13 +153,27 @@ public class RegattaActivity extends AbstractActivity { boolean autoExpandPreSelectedRace = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); + boolean showCompetitorSailIdColumn = true; + boolean showCompetitorFullNameColumn = true; + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS)) { + String value = parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS).get(0); + if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_FULL_NAME)) { + showCompetitorSailIdColumn = false; + } else if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_SAIL_ID)) { + showCompetitorFullNameColumn = false; + } else if (value.trim().equals("")) { + showCompetitorFullNameColumn = false; + showCompetitorSailIdColumn = false; + } + } result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, /* namesOfRaceColumnsToShow */ null, namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ (namesOfRacesToShow != null && !namesOfRacesToShow.isEmpty()) ? namesOfRacesToShow.get(0) : null, /* ascending */ true, /* updateUponPlayStateChange */ raceDetails.isEmpty() && legDetails.isEmpty(), - raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor); + raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor, + showCompetitorSailIdColumn, showCompetitorFullNameColumn); } else { final List overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java index 46649dcbd4c..d781e2cfa12 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java @@ -154,13 +154,27 @@ public class SeriesActivity extends AbstractActivity { boolean autoExpandPreSelectedRace = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); + boolean showCompetitorSailIdColumn = true; + boolean showCompetitorFullNameColumn = true; + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS)) { + String value = parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS).get(0); + if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_FULL_NAME)) { + showCompetitorSailIdColumn = false; + } else if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_SAIL_ID)) { + showCompetitorFullNameColumn = false; + } else if (value.trim().equals("")) { + showCompetitorFullNameColumn = false; + showCompetitorSailIdColumn = false; + } + } result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, /* namesOfRaceColumnsToShow */ null, namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ (namesOfRacesToShow != null && !namesOfRacesToShow.isEmpty()) ? namesOfRacesToShow.get(0) : null, /* ascending */ true, /* updateUponPlayStateChange */ raceDetails.isEmpty() && legDetails.isEmpty(), - raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor); + raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor, + showCompetitorSailIdColumn, showCompetitorFullNameColumn); } else { final List overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 7c30ad62231..a80c97ceb65 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1149,4 +1149,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String sendingMailsFailed(); String sendingMailsSuccessfull(); String selectEventForInvitation(); + String showCompetitorSailIdColumn(); + String showCompetitorFullNameColumn(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 2f7373b6bab..04b1a5a12a3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1129,4 +1129,6 @@ inviteCompetitors=Invite competitors notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail sendingMailsFailed=Sending Mails failed sendingMailsSuccessfull=Successfully sent E-Mails -selectEventForInvitation=Select Event for Invitation \ No newline at end of file +selectEventForInvitation=Select Event for Invitation +showCompetitorSailIdColumn=Show Competitor Sail Id +showCompetitorFullNameColumn=Show Competitor Full Name \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index ad69cfd2349..15977fbeb18 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1119,4 +1119,6 @@ inviteCompetitors=Teilnehmer einladen notAllCompetitorsProvideEmail=Nicht alle Teilnehmer haben eine E-Mail angegeben sendingMailsFailed=Versenden der E-Mails fehlgeschlagen sendingMailsSuccessfull=Versenden der E-Mails war erfolgreich! -selectEventForInvitation=Event für Einladung auswählen \ No newline at end of file +selectEventForInvitation=Event für Einladung auswählen +showCompetitorSailIdColumn=Sail Id anzeigen +showCompetitorFullNameColumn=Vollen Namen anzeigen \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 2b46014b206..192855dd86c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1068,4 +1068,6 @@ inviteCompetitors=приглашать УчаÑ�тники notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail sendingMailsFailed=Sending Mails failed sendingMailsSuccessfull=Successfully sent E-Mails -selectEventForInvitation=Select Event for Invitation \ No newline at end of file +selectEventForInvitation=Select Event for Invitation +showCompetitorSailIdColumn=Show Competitor Sail Id +showCompetitorFullNameColumn=Show Competitor Full Name \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 5220df45d3c..710e03455c4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -214,7 +214,8 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_NAME) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_LEG_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL) || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) || - parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED)) { + parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_OVERALL_COLUMN_WITH_NUMBER_OF_RACES_COMPLETED) + || parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS)) { List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL)); List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_DETAIL)); List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL)); @@ -227,13 +228,27 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { boolean autoExpandPreSelectedRace = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE) ? Boolean.valueOf(parameterMap.get(LeaderboardUrlSettings.PARAM_AUTO_EXPAND_PRESELECTED_RACE).get(0)) : (namesOfRacesToShow != null && namesOfRacesToShow.size() == 1); + boolean showCompetitorSailIdColumn = true; + boolean showCompetitorFullNameColumn = true; + if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS)) { + String value = parameterMap.get(LeaderboardUrlSettings.PARAM_SHOW_COMPETITOR_NAME_COLUMNS).get(0); + if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_FULL_NAME)) { + showCompetitorSailIdColumn = false; + } else if (value.equals(LeaderboardUrlSettings.COMPETITOR_NAME_COLUMN_SAIL_ID)) { + showCompetitorFullNameColumn = false; + } else if (value.trim().equals("")) { + showCompetitorFullNameColumn = false; + showCompetitorSailIdColumn = false; + } + } result = new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetails, /* namesOfRaceColumnsToShow */ null, namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ (namesOfRacesToShow != null && !namesOfRacesToShow.isEmpty()) ? namesOfRacesToShow.get(0) : null, /* ascending */ true, /* updateUponPlayStateChange */ raceDetails.isEmpty() && legDetails.isEmpty(), - raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor); + raceColumnSelectionStrategy, showAddedScores, showOverallColumnWithNumberOfRacesSailedPerCompetitor, + showCompetitorSailIdColumn, showCompetitorFullNameColumn); } else { final List overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index d0f2c412916..f78013ff523 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -225,6 +225,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * but the cumulative score up to that race. */ private boolean showAddedScores; + + private boolean showCompetitorSailId; + private boolean showCompetitorFullName; /** * When true then an additional column just before the overall points is displayed that sums up @@ -1734,6 +1737,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS leaderboardAsTableSelectionModelRegistration = leaderboardSelectionModel.addSelectionChangeHandler(selectionChangeHandler); leaderboardTable.setSelectionModel(leaderboardSelectionModel, selectionCheckboxColumn.getSelectionManager()); setShowAddedScores(settings.isShowAddedScores()); + setShowCompetitorSailId(settings.isShowCompetitorSailIdColumn()); + setShowCompetitorFullName(settings.isShowCompetitorFullNameColumn()); setShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(settings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor()); if (timer.isInitialized()) { loadCompleteLeaderboard(getLeaderboardDisplayDate()); @@ -2029,6 +2034,22 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS this.showAddedScores = showAddedScores; } + private boolean isShowCompetitorSailId() { + return showCompetitorSailId; + } + + private void setShowCompetitorSailId(boolean showCompetitorSailId) { + this.showCompetitorSailId = showCompetitorSailId; + } + + private boolean isShowCompetitorFullName() { + return showCompetitorFullName; + } + + private void setShowCompetitorFullName(boolean showCompetitorFullName) { + this.showCompetitorFullName = showCompetitorFullName; + } + /** * The time point for which the leaderboard currently shows results. In {@link PlayModes#Replay replay mode} this is * the {@link #timer}'s time point. In {@link PlayModes#Live live mode} the {@link #timer}'s time is quantized to @@ -2988,7 +3009,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS Collections.unmodifiableList(selectedLegDetails), Collections.unmodifiableList(selectedRaceDetails), Collections.unmodifiableList(selectedOverallDetailColumns), /* All races to select */ leaderboard.getRaceList(), raceColumnSelection.getSelectedRaceColumnsOrderedAsInLeaderboard(leaderboard), raceColumnSelection, autoExpandPreSelectedRace, - isShowAddedScores(), timer.getRefreshInterval(), isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(), stringMessages); + isShowAddedScores(), timer.getRefreshInterval(), isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(), + isShowCompetitorSailId(), isShowCompetitorFullName(), stringMessages); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java index d97d4fb8152..a1e52983096 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettings.java @@ -85,6 +85,9 @@ public class LeaderboardSettings { */ private final boolean showAddedScores; + private final boolean showCompetitorSailIdColumn; + private final boolean showCompetitorFullNameColumn; + /** * Show a column with total number of races completed */ @@ -98,7 +101,8 @@ public class LeaderboardSettings { List namesOfRaceColumnsToShow, List namesOfRacesToShow, Integer numberOfLastRacesToShow, boolean autoExpandPreSelectedRace, Long delayBetweenAutoAdvancesInMilliseconds, String nameOfRaceToSort, boolean sortAscending, boolean updateUponPlayStateChange, RaceColumnSelectionStrategies activeRaceColumnSelectionStrategy, - boolean showAddedScores, boolean showOverallColumnWithNumberOfRacesCompletedPerCompetitor) { + boolean showAddedScores, boolean showOverallColumnWithNumberOfRacesCompletedPerCompetitor, + boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn) { if (namesOfRacesToShow != null && namesOfRaceColumnsToShow != null) { throw new IllegalArgumentException("You can identify races either only by their race or by their column names, not both"); } @@ -116,6 +120,8 @@ public class LeaderboardSettings { this.sortAscending = sortAscending; this.updateUponPlayStateChange = updateUponPlayStateChange; this.showAddedScores = showAddedScores; + this.showCompetitorSailIdColumn = showCompetitorSailIdColumn; + this.showCompetitorFullNameColumn = showCompetitorFullNameColumn; this.showOverallColumnWithNumberOfRacesCompletedPerCompetitor = showOverallColumnWithNumberOfRacesCompletedPerCompetitor; } @@ -198,4 +204,12 @@ public class LeaderboardSettings { public boolean isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor() { return showOverallColumnWithNumberOfRacesCompletedPerCompetitor; } + + public boolean isShowCompetitorSailIdColumn() { + return showCompetitorSailIdColumn; + } + + public boolean isShowCompetitorFullNameColumn() { + return showCompetitorFullNameColumn; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java index 6e7a4414627..c49feffacdd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java @@ -48,6 +48,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone private final boolean autoExpandPreSelectedRace; private final boolean showAddedScores; private final boolean showOverallColumnWithNumberOfRacesSailedPerCompetitor; + private final boolean showCompetitorSailIdColumn; + private final boolean showCompetitorFullNameColumn; private final long delayBetweenAutoAdvancesInMilliseconds; private final Integer numberOfLastRacesToShow; private RaceColumnSelectionStrategies activeRaceColumnSelectionStrategy; @@ -56,6 +58,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone private IntegerBox numberOfLastRacesToShowBox; private CheckBox showAddedScoresCheckBox; private CheckBox showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox; + private CheckBox showCompetitorSailIdColumnheckBox; + private CheckBox showCompetitorFullNameColumnCheckBox; protected LeaderboardSettingsDialogComponent(List maneuverDetailSelection, List legDetailSelection, List raceDetailSelection, @@ -63,6 +67,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone Iterable raceColumnSelection, RaceColumnSelection raceColumnSelectionStrategy, boolean autoExpandPreSelectedRace, boolean showAddedScores, long delayBetweenAutoAdvancesInMilliseconds, boolean showOverallColumnWithNumberOfRacesSailedPerCompetitor, + boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn, StringMessages stringMessages) { this.raceAllRaceColumns = raceAllRaceColumns; this.numberOfLastRacesToShow = raceColumnSelectionStrategy.getNumberOfLastRaceColumnsToShow(); @@ -82,6 +87,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone this.delayBetweenAutoAdvancesInMilliseconds = delayBetweenAutoAdvancesInMilliseconds; this.showAddedScores = showAddedScores; this.showOverallColumnWithNumberOfRacesSailedPerCompetitor = showOverallColumnWithNumberOfRacesSailedPerCompetitor; + this.showCompetitorSailIdColumn = showCompetitorSailIdColumn; + this.showCompetitorFullNameColumn = showCompetitorFullNameColumn; } @Override @@ -184,6 +191,12 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone dialog.addTooltip(showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox, stringMessages.showNumberOfRacesScored()); showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox.setValue(showOverallColumnWithNumberOfRacesSailedPerCompetitor); overallDetailDialogContent.add(showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox); + showCompetitorSailIdColumnheckBox = dialog.createCheckbox(stringMessages.showCompetitorSailIdColumn()); + showCompetitorSailIdColumnheckBox.setValue(showCompetitorSailIdColumn); + overallDetailDialogContent.add(showCompetitorSailIdColumnheckBox); + showCompetitorFullNameColumnCheckBox = dialog.createCheckbox(stringMessages.showCompetitorFullNameColumn()); + showCompetitorFullNameColumnCheckBox.setValue(showCompetitorFullNameColumn); + overallDetailDialogContent.add(showCompetitorFullNameColumnCheckBox); overallDetailDialog.add(overallDetailDialogContent); return overallDetailDialog; } @@ -371,7 +384,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone null, true, /* updateUponPlayStateChange */ true, activeRaceColumnSelectionStrategy, /*showAddedScores*/ showAddedScoresCheckBox.getValue().booleanValue(), - /*showOverallColumnWithNumberOfRacesSailedPerCompetitor*/ showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox.getValue().booleanValue()); + /*showOverallColumnWithNumberOfRacesSailedPerCompetitor*/ showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox.getValue().booleanValue(), + showCompetitorSailIdColumnheckBox.getValue(), showCompetitorFullNameColumnCheckBox.getValue()); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java index fff017843fd..6a079e5e8b6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java @@ -76,7 +76,8 @@ public class LeaderboardSettingsFactory { /* set autoExpandPreSelectedRace to true if we look at a single race */ nameOfRaceColumnToShow != null || nameOfRaceToShow != null, /* refresh interval */ null, /* name of race to sort */ nameOfRaceToSort, /* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelection.getType(), - /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false); + /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false, + /*showCompetitorSailIdColumns*/true, /*showCompetitorFullNameColumn*/true); break; case Replay: settings = createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, nameOfRaceToSort, /* autoExpandFirstRace */ @@ -165,7 +166,8 @@ public class LeaderboardSettingsFactory { namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ nameOfRaceToSort, /* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelectionStrategy, - /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false); + /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false, + /*showCompetitorSailIdColumns*/true, /*showCompetitorFullNameColumn*/true); } public LeaderboardSettings mergeLeaderboardSettings(LeaderboardSettings settingsWithRaceSelection, LeaderboardSettings settingsWithDetails) { @@ -180,6 +182,8 @@ public class LeaderboardSettingsFactory { List namesOfRacesToShow = copyRaceNamesList(settingsWithRaceSelection.getNamesOfRacesToShow()); Integer numberOfLastRacesToShow = settingsWithRaceSelection.getNumberOfLastRacesToShow(); boolean autoExpandPreSelectedRace = settingsWithRaceSelection.isAutoExpandPreSelectedRace(); + boolean showCompetitorSailIdColumn = settingsWithRaceSelection.isShowCompetitorSailIdColumn(); + boolean showCompetitorFullNameColumns = settingsWithRaceSelection.isShowCompetitorFullNameColumn(); String nameOfRaceToSort = settingsWithRaceSelection.getNameOfRaceToSort(); boolean sortAscending = settingsWithRaceSelection.isSortAscending(); boolean updateUponPlayStateChange = settingsWithRaceSelection.isUpdateUponPlayStateChange(); @@ -187,7 +191,7 @@ public class LeaderboardSettingsFactory { return new LeaderboardSettings(maneuverDetails, legDetails, raceDetails, overallDetailsToShow, namesOfRaceColumnsToShow, namesOfRacesToShow, numberOfLastRacesToShow, autoExpandPreSelectedRace, refreshIntervalInMs, nameOfRaceToSort, sortAscending, updateUponPlayStateChange, strategy, /*showAddedScores*/ false, - /*showOverallRacesCompleted*/ false); + /*showOverallRacesCompleted*/ false, showCompetitorSailIdColumn, showCompetitorFullNameColumns); } private List copyDetailTypes(List detailTypes) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java index 0f0a6734a3b..24d783947f6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java @@ -112,7 +112,8 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog Date: Thu, 26 Feb 2015 16:00:58 +0100 Subject: [PATCH 213/264] Ignoring sail id and competitor name column if settings tell us to do so --- .../gwt/ui/leaderboard/LeaderboardPanel.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index f78013ff523..c3687e27d3e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -2818,21 +2818,28 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS */ private int ensureSailIDAndCompetitorColumn(int sailIdColumnIndex) { final int nextColumnIndex; + int columnCounter = 0; if (getLeaderboardTable().getColumnCount() <= sailIdColumnIndex) { - addColumn(new SailIDColumn(new CompetitorFetcher() { - @Override - public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { - return t.competitor; - } - })); - addColumn(createCompetitorColumn()); + if (isShowCompetitorSailId()) { + addColumn(new SailIDColumn(new CompetitorFetcher() { + @Override + public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { + return t.competitor; + } + })); + columnCounter++; + } + if (isShowCompetitorFullName()) { + addColumn(createCompetitorColumn()); + columnCounter++; + } } else { if (!(getLeaderboardTable().getColumn(sailIdColumnIndex) instanceof SailIDColumn)) { throw new RuntimeException("The second column must always be the sail ID column but it was of type " + getLeaderboardTable().getColumn(sailIdColumnIndex).getClass().getName()); } } - nextColumnIndex = sailIdColumnIndex + 2; + nextColumnIndex = sailIdColumnIndex + columnCounter; return nextColumnIndex; } From cc5b99ed8f5be56fdfb48536a2352533024fc987 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 26 Feb 2015 16:23:10 +0100 Subject: [PATCH 214/264] moved SailingPermissionsForRoleProvider to com.sap.sailing.domain.common so that gateway and gwt.ui can both use them in their shiro.ini --- java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF | 4 +++- .../com/sap/sailing/domain/common}/security/Permission.java | 2 +- .../src/com/sap/sailing/domain/common}/security/Roles.java | 2 +- .../common}/security/SailingPermissionsForRoleProvider.java | 2 +- .../sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java | 4 ++-- .../gwt/ui/client/media/MediaPlayerManagerComponent.java | 2 +- java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini | 2 +- java/com.sap.sailing.server.gateway/resources/shiro.ini | 2 +- 8 files changed, 11 insertions(+), 9 deletions(-) rename java/{com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared => com.sap.sailing.domain.common/src/com/sap/sailing/domain/common}/security/Permission.java (89%) rename java/{com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared => com.sap.sailing.domain.common/src/com/sap/sailing/domain/common}/security/Roles.java (84%) rename java/{com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared => com.sap.sailing.domain.common/src/com/sap/sailing/domain/common}/security/SailingPermissionsForRoleProvider.java (89%) diff --git a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF index 7bd94febbcb..ebce249046e 100644 --- a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF @@ -20,6 +20,8 @@ Export-Package: com.sap.sailing.domain.common, com.sap.sailing.domain.common.racelog.tracking, com.sap.sailing.domain.common.racelog.utils, com.sap.sailing.domain.common.scalablevalue.impl, + com.sap.sailing.domain.common.security, com.sap.sailing.domain.common.trackfiles Require-Bundle: com.sap.sse.datamining.shared, - com.sap.sse.common + com.sap.sse.common, + com.sap.sse.security.common diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Permission.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java similarity index 89% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Permission.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java index f55f5a272d9..0a542a58534 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Permission.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.client.shared.security; +package com.sap.sailing.domain.common.security; public enum Permission implements com.sap.sse.security.shared.Permission { MANAGE_EVENTS, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Roles.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Roles.java similarity index 84% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Roles.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Roles.java index ef6ffae1f98..5d54d40edd2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/Roles.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Roles.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.client.shared.security; +package com.sap.sailing.domain.common.security; import com.sap.sse.security.shared.Role; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SailingPermissionsForRoleProvider.java similarity index 89% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SailingPermissionsForRoleProvider.java index 6b26b66c14d..7300e408977 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/security/SailingPermissionsForRoleProvider.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/SailingPermissionsForRoleProvider.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.ui.client.shared.security; +package com.sap.sailing.domain.common.security; import java.util.Collections; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index 2b516915b5e..fbc4d4f6105 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -9,6 +9,8 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.google.gwt.user.client.ui.TabLayoutPanel; +import com.sap.sailing.domain.common.security.Permission; +import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider; import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer; import com.sap.sailing.gwt.ui.client.LeaderboardGroupsRefresher; @@ -19,8 +21,6 @@ import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; -import com.sap.sailing.gwt.ui.client.shared.security.Permission; -import com.sap.sailing.gwt.ui.client.shared.security.SailingPermissionsForRoleProvider; import com.sap.sailing.gwt.ui.masterdataimport.MasterDataImportPanel; import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java index 0f0d4a286cf..cc2803d896c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java @@ -25,6 +25,7 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.media.MediaTrack.MediaType; import com.sap.sailing.domain.common.media.MediaTrack.Status; +import com.sap.sailing.domain.common.security.Roles; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -37,7 +38,6 @@ import com.sap.sailing.gwt.ui.client.media.shared.VideoPlayer; import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; -import com.sap.sailing.gwt.ui.client.shared.security.Roles; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.ErrorReporter; diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini index 2e902cfaee4..8997a7fd30a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/shiro.ini @@ -5,7 +5,7 @@ credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 # Realm configuration: -permissionsForRoleProvider = com.sap.sailing.gwt.ui.client.shared.security.SailingPermissionsForRoleProvider +permissionsForRoleProvider = com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher upRealm.permissionsForRoleProvider = $permissionsForRoleProvider diff --git a/java/com.sap.sailing.server.gateway/resources/shiro.ini b/java/com.sap.sailing.server.gateway/resources/shiro.ini index 53d24b13858..f1635ef547f 100755 --- a/java/com.sap.sailing.server.gateway/resources/shiro.ini +++ b/java/com.sap.sailing.server.gateway/resources/shiro.ini @@ -7,7 +7,7 @@ credentialsMatcher.hashIterations = 1024 # Realm configuration: # -------------------- -permissionsForRoleProvider = com.sap.sailing.gwt.ui.client.shared.security.SailingPermissionsForRoleProvider +permissionsForRoleProvider = com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider upRealm = com.sap.sse.security.UsernamePasswordRealm upRealm.credentialsMatcher = $credentialsMatcher upRealm.permissionsForRoleProvider = $permissionsForRoleProvider From 89fd76979a56ff78fd8b9256f6385e7bd3e4339c Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Thu, 26 Feb 2015 17:16:05 +0100 Subject: [PATCH 215/264] Preserving state of competitor column display state --- .../gwt/ui/leaderboard/LeaderboardPanel.java | 23 ++++++++++++++++--- .../LeaderboardSettingsFactory.java | 5 ++-- .../gwt/ui/raceboard/RaceBoardPanel.java | 3 ++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index c3687e27d3e..47f927a07e8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -227,7 +227,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private boolean showAddedScores; private boolean showCompetitorSailId; + private int competitorSailIdColumnIndex = -1; private boolean showCompetitorFullName; + private int competitorFullNameColumnIndex = -1; /** * When true then an additional column just before the overall points is displayed that sums up @@ -398,6 +400,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS settingsUpdatedExplicitly = true; } setShowAddedScores(newSettings.isShowAddedScores()); + setShowCompetitorSailId(newSettings.isShowCompetitorSailIdColumn()); + setShowCompetitorFullName(newSettings.isShowCompetitorFullNameColumn()); setShowOverallColumnWithNumberOfRacesCompletedPerCompetitor(newSettings.isShowOverallColumnWithNumberOfRacesCompletedPerCompetitor()); final List> columnsToExpandAgain = new ArrayList>(); for (int i = 0; i < getLeaderboardTable().getColumnCount(); i++) { @@ -2820,7 +2824,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS final int nextColumnIndex; int columnCounter = 0; if (getLeaderboardTable().getColumnCount() <= sailIdColumnIndex) { - if (isShowCompetitorSailId()) { + if (isShowCompetitorSailId() && competitorSailIdColumnIndex == -1) { addColumn(new SailIDColumn(new CompetitorFetcher() { @Override public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { @@ -2828,10 +2832,22 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } })); columnCounter++; + competitorSailIdColumnIndex = sailIdColumnIndex; + } else { + if (competitorSailIdColumnIndex != -1) { + getLeaderboardTable().removeColumn(competitorSailIdColumnIndex); + competitorSailIdColumnIndex = -1; + } } - if (isShowCompetitorFullName()) { + if (isShowCompetitorFullName() && competitorFullNameColumnIndex == -1) { addColumn(createCompetitorColumn()); + competitorFullNameColumnIndex = sailIdColumnIndex+columnCounter; columnCounter++; + } else { + if (competitorFullNameColumnIndex != -1) { + getLeaderboardTable().removeColumn(competitorFullNameColumnIndex); + competitorFullNameColumnIndex = -1; + } } } else { if (!(getLeaderboardTable().getColumn(sailIdColumnIndex) instanceof SailIDColumn)) { @@ -2977,7 +2993,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS : preSelectedRace.getRaceName(), /* don't change nameOfRaceColumnToShow */null, /* set nameOfRaceToShow if race was pre-selected */preSelectedRace == null ? null : preSelectedRace - .getRaceName(), getRaceColumnSelection(), /* leave showRegattaRank and overall details unchanged */ null)); + .getRaceName(), getRaceColumnSelection(), /* leave showRegattaRank and overall details unchanged */ null, + /* take into account state of competitor columns*/isShowCompetitorSailId(), isShowCompetitorFullName())); } currentlyHandlingPlayStateChange = false; oldPlayMode = playMode; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java index 6a079e5e8b6..f4036493976 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java @@ -45,7 +45,8 @@ public class LeaderboardSettingsFactory { * overall details if and only if this parameter is true */ public LeaderboardSettings createNewSettingsForPlayMode(PlayModes playMode, String nameOfRaceToSort, String nameOfRaceColumnToShow, - String nameOfRaceToShow, RaceColumnSelection raceColumnSelection, Boolean showRegattaRank) { + String nameOfRaceToShow, RaceColumnSelection raceColumnSelection, Boolean showRegattaRank, boolean showCompetitorSailIdColumn, + boolean showCompetitorNameColumn) { if (nameOfRaceColumnToShow != null && nameOfRaceToShow != null) { throw new IllegalArgumentException("Can identify only one race to show, either by race name or by its column name, but not both"); } @@ -77,7 +78,7 @@ public class LeaderboardSettingsFactory { /* refresh interval */ null, /* name of race to sort */ nameOfRaceToSort, /* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelection.getType(), /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false, - /*showCompetitorSailIdColumns*/true, /*showCompetitorFullNameColumn*/true); + /*showCompetitorSailIdColumns*/showCompetitorSailIdColumn, /*showCompetitorFullNameColumn*/showCompetitorNameColumn); break; case Replay: settings = createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, nameOfRaceToSort, /* autoExpandFirstRace */ diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java index a6e380068e6..163575eaadd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java @@ -252,7 +252,8 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra .createNewSettingsForPlayMode(timer.getPlayMode(), /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(), /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(), - new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false); + new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false, + /*showCompetitorSailIdColumn*/true, /*showCompetitorFullNameColumn*/true); return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier, competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, userAgent, /* showRaceDetails */ true, competitorSearchTextBox, From 6e5ae81edf046309e5eee4084505f53883315a1a Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Thu, 26 Feb 2015 17:50:20 +0100 Subject: [PATCH 216/264] Applying settings by URL and Dialog now works correctly and retains the right order of columns --- .../gwt/ui/leaderboard/LeaderboardPanel.java | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index 47f927a07e8..9961829d75b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -2823,37 +2823,40 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private int ensureSailIDAndCompetitorColumn(int sailIdColumnIndex) { final int nextColumnIndex; int columnCounter = 0; - if (getLeaderboardTable().getColumnCount() <= sailIdColumnIndex) { - if (isShowCompetitorSailId() && competitorSailIdColumnIndex == -1) { + int leaderboardColumnCount = getLeaderboardTable().getColumnCount(); + if (isShowCompetitorSailId() && competitorSailIdColumnIndex == -1) { + competitorSailIdColumnIndex = sailIdColumnIndex; + if (leaderboardColumnCount > 1) { + insertColumn(competitorSailIdColumnIndex, new SailIDColumn(new CompetitorFetcher() { + @Override + public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { + return t.competitor; + } + })); + } else { addColumn(new SailIDColumn(new CompetitorFetcher() { @Override public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { return t.competitor; } })); - columnCounter++; - competitorSailIdColumnIndex = sailIdColumnIndex; - } else { - if (competitorSailIdColumnIndex != -1) { - getLeaderboardTable().removeColumn(competitorSailIdColumnIndex); - competitorSailIdColumnIndex = -1; - } } - if (isShowCompetitorFullName() && competitorFullNameColumnIndex == -1) { + columnCounter++; + } else if (!isShowCompetitorSailId() && competitorSailIdColumnIndex != -1) { + removeColumn(competitorSailIdColumnIndex); + competitorSailIdColumnIndex = -1; + } + if (isShowCompetitorFullName() && competitorFullNameColumnIndex == -1) { + if (leaderboardColumnCount > 1) { + insertColumn(sailIdColumnIndex+columnCounter, createCompetitorColumn()); + } else { addColumn(createCompetitorColumn()); - competitorFullNameColumnIndex = sailIdColumnIndex+columnCounter; - columnCounter++; - } else { - if (competitorFullNameColumnIndex != -1) { - getLeaderboardTable().removeColumn(competitorFullNameColumnIndex); - competitorFullNameColumnIndex = -1; - } - } - } else { - if (!(getLeaderboardTable().getColumn(sailIdColumnIndex) instanceof SailIDColumn)) { - throw new RuntimeException("The second column must always be the sail ID column but it was of type " - + getLeaderboardTable().getColumn(sailIdColumnIndex).getClass().getName()); } + columnCounter++; + competitorFullNameColumnIndex = sailIdColumnIndex+columnCounter; + } else if (!isShowCompetitorFullName() && competitorFullNameColumnIndex != -1) { + removeColumn(competitorFullNameColumnIndex); + competitorFullNameColumnIndex = -1; } nextColumnIndex = sailIdColumnIndex + columnCounter; return nextColumnIndex; From fda2a66dce97525f2a37bfaee96b05980484bab2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 27 Feb 2015 13:50:34 +0100 Subject: [PATCH 217/264] on the way to a generic file storage service in the sailing gateway bundle; preparing for file upload from AdminConsole --- .../jaxrs/api/FileStorageResource.java | 101 ++++++++++++++++++ .../impl/AmazonS3FileStorageServiceImpl.java | 1 + .../impl/LocalFileStorageServiceImpl.java | 1 + .../sse/gwt/adminconsole/StringMessages.java | 7 ++ .../adminconsole/URLFieldWithFileUpload.java | 43 ++++++++ .../Security UI sdm.launch | 8 +- 6 files changed, 156 insertions(+), 5 deletions(-) create mode 100755 java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java create mode 100755 java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java create mode 100755 java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java new file mode 100755 index 00000000000..4251cb730e4 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java @@ -0,0 +1,101 @@ +package com.sap.sailing.server.gateway.jaxrs.api; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.json.simple.JSONObject; + +import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; +import com.sap.sse.filestorage.InvalidPropertiesException; +import com.sap.sse.filestorage.OperationFailedException; + +@Path("/v1/file") +public class FileStorageResource extends AbstractSailingServerResource { + private static final Logger logger = Logger.getLogger(FileStorageResource.class.getName()); + + public static final String JSON_FILE_URI = "file_uri"; + + /** + * The maximum size of an image uploaded by a user as a team image, in megabytes (1024*1024 bytes) + */ + private static final int MAX_SIZE_IN_MB = 5; + + /** + * Expects the HTTP header {@code Content-Length} to be set. + */ + // + // example for testing upload: + // $ curl -v -H "Content-Type:image/jpeg" \ + // --data-binary @ \ + // http://127.0.0.1:8888/sailingserver/api/v1/file + @POST + @Consumes({ "image/jpeg", "image/png" }) + @Produces("application/json;charset=UTF-8") + public Response setTeamImage(InputStream uploadedInputStream, + @HeaderParam("Content-Type") String fileType, @HeaderParam("Content-Length") long sizeInBytes) { + final JSONObject result = new JSONObject(); + Response response; + String fileExtension = ""; + if (fileType.equals("image/jpeg")) { + fileExtension = ".jpg"; + } else if (fileType.equals("image/png")) { + fileExtension = ".png"; + } + + URI fileUri; + try { + if (sizeInBytes > 1024 * 1024 * MAX_SIZE_IN_MB) { + throw new WebApplicationException(Response.status(Status.BAD_REQUEST) + .entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build()); + } + fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() + .storeFile(uploadedInputStream, fileExtension, sizeInBytes); + result.put(JSON_FILE_URI, fileUri.toString()); + response = Response.ok().entity(result.toJSONString()).build(); + } catch (IOException | OperationFailedException | InvalidPropertiesException e) { + final String errorMessage = "Could not store file: "+e.getMessage(); + logger.log(Level.WARNING, "Could not store file", e); + result.put("status", Status.INTERNAL_SERVER_ERROR.name()); + result.put("message", errorMessage); + response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(result.toJSONString()).build(); + } + return response; + } + + @DELETE + @Produces("application/json;charset=UTF-8") + public Response deleteImage(@PathParam("uri") String uri) { + final JSONObject result = new JSONObject(); + Response response; + try { + getService().getFileStorageManagementService().getActiveFileStorageService() + .removeFile(new URI(uri)); + result.put("status", Status.OK.name()); + response = Response.ok().entity(result.toJSONString()).build(); + } catch (NoCorrespondingServiceRegisteredException | OperationFailedException | InvalidPropertiesException + | URISyntaxException e) { + final String errorMessage = "Could not delete competitor image "+uri+": "+e.getMessage(); + logger.log(Level.WARNING, "Could not delete competitor image "+uri, e); + result.put("status", Status.BAD_REQUEST.name()); + result.put("message", errorMessage); + response = Response.status(Status.BAD_REQUEST).entity(result.toJSONString()).build(); + } + return response; + } +} diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java index 6a108c8a84b..f4758b229c1 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java @@ -97,6 +97,7 @@ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl i final ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(lengthInBytes); final String key = getKey(fileExtension); + // TODO bug 2583: use something like SecurityUtil.getSubject().checkPermission("file:store:"+key) final PutObjectRequest request = new PutObjectRequest(bucketName.getValue(), key, is, metadata) .withCannedAcl(CannedAccessControlList.PublicRead); final AmazonS3Client s3Client = createS3Client(); diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/LocalFileStorageServiceImpl.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/LocalFileStorageServiceImpl.java index c9d09c02275..d164486630c 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/LocalFileStorageServiceImpl.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/LocalFileStorageServiceImpl.java @@ -55,6 +55,7 @@ public class LocalFileStorageServiceImpl extends BaseFileStorageServiceImpl impl OutputStream outputStream = null; String fileName = getKey(fileExtension); String pathToFile = localPath.getValue() + "/" + fileName; + // TODO bug 2583: use something like SecurityUtil.getSubject().checkPermission("file:store:"+pathToFile) File outputFile = new File(pathToFile); logger.log(Level.FINE, "Storing file in " + outputFile.getAbsolutePath()); diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java new file mode 100755 index 00000000000..2bd67b7e06b --- /dev/null +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java @@ -0,0 +1,7 @@ +package com.sap.sse.gwt.adminconsole; + +import com.google.gwt.i18n.client.Messages; + +public interface StringMessages extends Messages { + String upload(); +} diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java new file mode 100755 index 00000000000..f112be8d3ca --- /dev/null +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -0,0 +1,43 @@ +package com.sap.sse.gwt.adminconsole; + +import com.google.gwt.dom.client.InputElement; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONParser; +import com.google.gwt.user.client.ui.FileUpload; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent; +import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.TextBox; + +/** + * A {@link TextBox} together with an upload button that can use the file storage service to + * save the file and produce a URL for it which is then taken over into the text field. + * + * @author Axel Uhl (d043530) + * + */ +public class URLFieldWithFileUpload extends HorizontalPanel { + private final TextBox urlTextBox; + + private final FormPanel formPanel; + + private final FileUpload fileUploadButton; + + public URLFieldWithFileUpload(StringMessages stringMessages, String fileUploadURL) { + urlTextBox = new TextBox(); + add(urlTextBox); + formPanel = new FormPanel(fileUploadURL); + fileUploadButton = new FileUpload(); + final InputElement inputElement = fileUploadButton.getElement().cast(); + inputElement.setName(stringMessages.upload()); + add(formPanel); + formPanel.addSubmitCompleteHandler(new SubmitCompleteHandler() { + @Override + public void onSubmitComplete(SubmitCompleteEvent event) { + String result = event.getResults(); + JSONObject resultJson = (JSONObject) JSONParser.parseLenient(result); + } + }); + } +} diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch index 7750b7dbad8..dede2cd8d60 100755 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -5,12 +5,10 @@ - - - + - + @@ -43,7 +41,7 @@ - + From e5e693beb2038b0c30be348eff44f13d741242ce Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 27 Feb 2015 14:12:22 +0100 Subject: [PATCH 218/264] fixed bug in LocalFileStorageServiceImpl.removeFile regarding the missing getValue() on localPath --- .../sailing/gwt/ui/server/SailingServiceImpl.java | 4 ++-- .../gateway/jaxrs/api/FileStorageResource.java | 10 +++++----- .../server/gateway/jaxrs/api/RestApiApplication.java | 1 + .../com/sap/sse/filestorage/FileStorageService.java | 4 ++-- .../impl/LocalFileStorageServiceImpl.java | 12 ++++++++---- .../filestorage/FileStorageManagementGwtService.java | 3 ++- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index efe9616b827..38ee1c2fdc4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -768,7 +768,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S regattaDTO.races = convertToRaceDTOs(regatta); regattaDTO.series = convertToSeriesDTOs(regatta); regattaDTO.startDate = regatta.getStartDate() != null ? regatta.getStartDate().asDate() : null; - regattaDTO.endDate = regatta.getStartDate() != null ? regatta.getEndDate().asDate() : null; + regattaDTO.endDate = regatta.getEndDate() != null ? regatta.getEndDate().asDate() : null; BoatClass boatClass = regatta.getBoatClass(); if (boatClass != null) { regattaDTO.boatClass = new BoatClassDTO(boatClass.getName(), boatClass.getDisplayName(), boatClass.getHullLength().getMeters()); @@ -5393,7 +5393,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public FileStorageServicePropertyErrorsDTO testFileStorageServiceProperties(String serviceName, String localeInfoName) { + public FileStorageServicePropertyErrorsDTO testFileStorageServiceProperties(String serviceName, String localeInfoName) throws IOException { try { FileStorageService service = getFileStorageService(serviceName); if (service != null) { diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java index 4251cb730e4..f0dddc298f7 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java @@ -12,8 +12,8 @@ import javax.ws.rs.DELETE; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.Path; -import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -80,7 +80,7 @@ public class FileStorageResource extends AbstractSailingServerResource { @DELETE @Produces("application/json;charset=UTF-8") - public Response deleteImage(@PathParam("uri") String uri) { + public Response deleteImage(@QueryParam("uri") String uri) { final JSONObject result = new JSONObject(); Response response; try { @@ -89,9 +89,9 @@ public class FileStorageResource extends AbstractSailingServerResource { result.put("status", Status.OK.name()); response = Response.ok().entity(result.toJSONString()).build(); } catch (NoCorrespondingServiceRegisteredException | OperationFailedException | InvalidPropertiesException - | URISyntaxException e) { - final String errorMessage = "Could not delete competitor image "+uri+": "+e.getMessage(); - logger.log(Level.WARNING, "Could not delete competitor image "+uri, e); + | URISyntaxException | IOException e) { + final String errorMessage = "Could not delete file "+uri+": "+e.getMessage(); + logger.log(Level.WARNING, "Could not delete file "+uri, e); result.put("status", Status.BAD_REQUEST.name()); result.put("message", errorMessage); response = Response.status(Status.BAD_REQUEST).entity(result.toJSONString()).build(); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java index 0002c3620a3..39f37809050 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java @@ -17,6 +17,7 @@ public class RestApiApplication extends Application { classes.add(SearchResource.class); classes.add(GPSFixesResource.class); classes.add(CompetitorsResource.class); + classes.add(FileStorageResource.class); return classes; } } diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/FileStorageService.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/FileStorageService.java index 9aa9f27b545..7c2668e7f75 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/FileStorageService.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/FileStorageService.java @@ -34,7 +34,7 @@ public interface FileStorageService extends IsManagedByCache Date: Fri, 27 Feb 2015 16:27:55 +0100 Subject: [PATCH 219/264] fixed string message stuff for competitor dialog and file upload --- .../META-INF/MANIFEST.MF | 3 +- .../ui/adminconsole/CompetitorEditDialog.java | 7 ++- .../sailing/gwt/ui/client/StringMessages.java | 7 ++- .../gwt/ui/client/StringMessages.properties | 6 +- .../ui/client/StringMessages_de.properties | 6 +- .../ui/client/StringMessages_ru.properties | 6 +- .../jaxrs/api/FileStorageResource.java | 2 + .../sse/gwt/adminconsole/StringMessages.java | 7 +++ .../adminconsole/URLFieldWithFileUpload.java | 56 ++++++++++++++++--- 9 files changed, 87 insertions(+), 13 deletions(-) diff --git a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF index 3e9aea38dba..4366e50b793 100644 --- a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF @@ -21,7 +21,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.declination, org.json.simple;bundle-version="1.1.0", com.sap.sailing.udpconnector, - com.googlecode.mgwt;bundle-version="2.0.0" + com.googlecode.mgwt;bundle-version="2.0.0", + com.sap.sse.gwt.adminconsole Bundle-ActivationPolicy: lazy Import-Package: org.osgi.framework;version="1.5.0", org.osgi.util.tracker diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index 91c37b7bb20..08e3f4992ec 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -20,6 +20,7 @@ import com.sap.sse.common.CountryCode; import com.sap.sse.common.CountryCodeFactory; import com.sap.sse.common.Util; import com.sap.sse.common.impl.RGBColor; +import com.sap.sse.gwt.adminconsole.URLFieldWithFileUpload; import com.sap.sse.gwt.client.dialog.DataEntryDialog; /** @@ -40,6 +41,7 @@ public class CompetitorEditDialog extends DataEntryDialog { private final TextBox sailId; private final TextBox email; private final StringMessages stringMessages; + private final URLFieldWithFileUpload imageUrlAndUploadComposite; public CompetitorEditDialog(final StringMessages stringMessages, CompetitorDTO competitorToEdit, DialogCallback callback) { @@ -121,6 +123,7 @@ public class CompetitorEditDialog extends DataEntryDialog { } } this.sailId = createTextBox(competitorToEdit.getSailID()); + imageUrlAndUploadComposite = new URLFieldWithFileUpload(stringMessages); } @@ -183,7 +186,7 @@ public class CompetitorEditDialog extends DataEntryDialog { @Override protected Widget getAdditionalWidget() { - Grid result = new Grid(6, 2); + Grid result = new Grid(7, 2); result.setWidget(0, 0, new Label(stringMessages.name())); result.setWidget(0, 1, name); result.setWidget(1, 0, new Label(stringMessages.sailNumber())); @@ -196,6 +199,8 @@ public class CompetitorEditDialog extends DataEntryDialog { result.setWidget(4, 1, email); result.setWidget(5, 0, new Label(stringMessages.boatClass())); result.setWidget(5, 1, boatClassName); + result.setWidget(6, 0, new Label(stringMessages.imageURL())); + result.setWidget(6, 1, imageUrlAndUploadComposite); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 7c30ad62231..580a8a2b84d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1,8 +1,9 @@ package com.sap.sailing.gwt.ui.client; import com.google.gwt.core.client.GWT; +import com.google.gwt.json.client.JSONValue; -public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { +public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, com.sap.sse.gwt.adminconsole.StringMessages { public static final StringMessages INSTANCE = GWT.create(StringMessages.class); String tracTracEvents(); @@ -1149,4 +1150,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String sendingMailsFailed(); String sendingMailsSuccessfull(); String selectEventForInvitation(); + String imageURL(); + String upload(); + String removeResult(JSONValue jsonValue, JSONValue jsonValue2); + String uploadSuccessful(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 2f7373b6bab..ccadff6adcf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1129,4 +1129,8 @@ inviteCompetitors=Invite competitors notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail sendingMailsFailed=Sending Mails failed sendingMailsSuccessfull=Successfully sent E-Mails -selectEventForInvitation=Select Event for Invitation \ No newline at end of file +selectEventForInvitation=Select Event for Invitation +imageURL=Image URL +upload=Upload +removeResult=Remove operation: {0} {1} +uploadSuccessful=Upload successful diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index ad69cfd2349..941eeed7472 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1119,4 +1119,8 @@ inviteCompetitors=Teilnehmer einladen notAllCompetitorsProvideEmail=Nicht alle Teilnehmer haben eine E-Mail angegeben sendingMailsFailed=Versenden der E-Mails fehlgeschlagen sendingMailsSuccessfull=Versenden der E-Mails war erfolgreich! -selectEventForInvitation=Event für Einladung auswählen \ No newline at end of file +selectEventForInvitation=Event für Einladung auswählen +imageURL=Bild-URL +upload=Hochladen +removeResult=Löschergebnis: {0} {1} +uploadSuccessful=Hochladen erfolgreich \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 2b46014b206..60e4997b279 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1068,4 +1068,8 @@ inviteCompetitors=приглашать УчаÑ�тники notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail sendingMailsFailed=Sending Mails failed sendingMailsSuccessfull=Successfully sent E-Mails -selectEventForInvitation=Select Event for Invitation \ No newline at end of file +selectEventForInvitation=Select Event for Invitation +imageURL=Image URL +upload=Upload +removeResult=Remove operation: {0} {1} +uploadSuccessful=Upload successful \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java index f0dddc298f7..40ceb741e09 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java @@ -78,6 +78,8 @@ public class FileStorageResource extends AbstractSailingServerResource { return response; } + // Example test use: + // curl -X DELETE http://127.0.0.1:8888/sailingserver/api/v1/file?uri=file:///c:/tmp/c7b821e1-ebab-4a96-a71d-28ac192b3e69.jpg @DELETE @Produces("application/json;charset=UTF-8") public Response deleteImage(@QueryParam("uri") String uri) { diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java index 2bd67b7e06b..d7986aede36 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java @@ -1,7 +1,14 @@ package com.sap.sse.gwt.adminconsole; import com.google.gwt.i18n.client.Messages; +import com.google.gwt.json.client.JSONValue; public interface StringMessages extends Messages { String upload(); + + String remove(); + + String removeResult(JSONValue jsonValue, JSONValue jsonValue2); + + String uploadSuccessful(); } diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java index f112be8d3ca..319dacf98f4 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -1,13 +1,19 @@ package com.sap.sse.gwt.adminconsole; import com.google.gwt.dom.client.InputElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONParser; +import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.FileUpload; import com.google.gwt.user.client.ui.FormPanel; import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent; import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler; import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.PushButton; +import com.google.gwt.user.client.ui.SubmitButton; import com.google.gwt.user.client.ui.TextBox; /** @@ -20,24 +26,60 @@ import com.google.gwt.user.client.ui.TextBox; public class URLFieldWithFileUpload extends HorizontalPanel { private final TextBox urlTextBox; - private final FormPanel formPanel; + private final FileUpload fileUploadField; - private final FileUpload fileUploadButton; + private String uri; + + private final PushButton removeButton; - public URLFieldWithFileUpload(StringMessages stringMessages, String fileUploadURL) { + public URLFieldWithFileUpload(final StringMessages stringMessages) { + final FormPanel removePanel = new FormPanel(); + removePanel.addSubmitCompleteHandler(new SubmitCompleteHandler() { + @Override + public void onSubmitComplete(SubmitCompleteEvent event) { + final JSONObject result = (JSONObject) JSONParser.parseLenient(event.getResults()); + Window.alert(stringMessages.removeResult(result.get("status"), result.get("message"))); + } + }); + removePanel.setMethod("DELETE"); + removeButton = new PushButton(new Image(com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon())); + removeButton.setEnabled(false); // the button shall only be enabled as long as we know the URI for removal + removeButton.ensureDebugId("RemoveButton"); + removeButton.setTitle(stringMessages.remove()); + removeButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + removePanel.setAction("/sailingserver/api/v1/file?uri="+uri); + removePanel.submit(); + } + }); urlTextBox = new TextBox(); add(urlTextBox); - formPanel = new FormPanel(fileUploadURL); - fileUploadButton = new FileUpload(); - final InputElement inputElement = fileUploadButton.getElement().cast(); - inputElement.setName(stringMessages.upload()); + FormPanel formPanel = new FormPanel(); + formPanel.setAction("/sailingserver/api/v1/file"); + formPanel.setEncoding(FormPanel.ENCODING_MULTIPART); + formPanel.setMethod(FormPanel.METHOD_POST); + fileUploadField = new FileUpload(); + final InputElement inputElement = fileUploadField.getElement().cast(); + inputElement.setName("file"); + SubmitButton submitButton = new SubmitButton(stringMessages.upload()); + formPanel.add(submitButton); add(formPanel); formPanel.addSubmitCompleteHandler(new SubmitCompleteHandler() { @Override public void onSubmitComplete(SubmitCompleteEvent event) { String result = event.getResults(); JSONObject resultJson = (JSONObject) JSONParser.parseLenient(result); + if (resultJson != null && resultJson.get("file_uri") != null) { + uri = resultJson.get("file_uri").toString(); + enableDelete(); + Window.alert(stringMessages.uploadSuccessful()); + } } }); } + + private void enableDelete() { + removeButton.setEnabled(true); + } } From e92a688be34d85f48387ccfcea7e7aa234af4d4e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 27 Feb 2015 17:40:41 +0100 Subject: [PATCH 220/264] using Jersey Multipart 1.17.0 which is now consistent with our Jersey version 1.17.0 coming from the Eclipse Orbit --- .../META-INF/MANIFEST.MF | 2 +- .../jaxrs/api/FileStorageResource.java | 31 +++++++++--------- .../features/target-base/feature.xml | 2 +- ...t-1.17.1.jar => jersey-multipart-1.17.jar} | Bin 52877 -> 52875 bytes .../adminconsole/URLFieldWithFileUpload.java | 7 +++- 5 files changed, 24 insertions(+), 18 deletions(-) rename java/com.sap.sailing.targetplatform.base/plugins/target-base/{jersey-multipart-1.17.1.jar => jersey-multipart-1.17.jar} (77%) mode change 100644 => 100755 diff --git a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF index 12fe41895b9..7798dd00600 100644 --- a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF @@ -48,7 +48,7 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.server.gateway.serialization.shared.android, com.sap.sse.shared.android, com.sap.sse.filestorage, - com.sun.jersey.contribs.jersey-multipart;bundle-version="1.17.1", + com.sun.jersey.contribs.jersey-multipart;bundle-version="1.17.0", com.sap.sse.security, org.apache.shiro.core;bundle-version="1.2.2", org.apache.shiro.web;bundle-version="1.2.2", diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java index 40ceb741e09..0fa71f32ea4 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java @@ -9,12 +9,12 @@ import java.util.logging.Logger; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; -import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -24,6 +24,8 @@ import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.filestorage.InvalidPropertiesException; import com.sap.sse.filestorage.OperationFailedException; +import com.sun.jersey.multipart.FormDataBodyPart; +import com.sun.jersey.multipart.FormDataParam; @Path("/v1/file") public class FileStorageResource extends AbstractSailingServerResource { @@ -45,37 +47,36 @@ public class FileStorageResource extends AbstractSailingServerResource { // --data-binary @ \ // http://127.0.0.1:8888/sailingserver/api/v1/file @POST - @Consumes({ "image/jpeg", "image/png" }) - @Produces("application/json;charset=UTF-8") - public Response setTeamImage(InputStream uploadedInputStream, - @HeaderParam("Content-Type") String fileType, @HeaderParam("Content-Length") long sizeInBytes) { + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces("text/html;charset=UTF-8") + public String uploadFile(@FormDataParam("file") InputStream uploadedInputStream, @FormDataParam("file") FormDataBodyPart filePart) { final JSONObject result = new JSONObject(); - Response response; String fileExtension = ""; - if (fileType.equals("image/jpeg")) { - fileExtension = ".jpg"; - } else if (fileType.equals("image/png")) { - fileExtension = ".png"; + MediaType fileType = filePart.getMediaType(); + if (fileType.getType().equals("image")) { + if (fileType.getSubtype().equals("jpeg")) { + fileExtension = ".jpg"; + } else if (fileType.getSubtype().equals("png")) { + fileExtension = ".png"; + } } URI fileUri; try { - if (sizeInBytes > 1024 * 1024 * MAX_SIZE_IN_MB) { + if (filePart.getContentDisposition().getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) { throw new WebApplicationException(Response.status(Status.BAD_REQUEST) .entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build()); } fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() - .storeFile(uploadedInputStream, fileExtension, sizeInBytes); + .storeFile(uploadedInputStream, fileExtension, filePart.getContentDisposition().getSize()); result.put(JSON_FILE_URI, fileUri.toString()); - response = Response.ok().entity(result.toJSONString()).build(); } catch (IOException | OperationFailedException | InvalidPropertiesException e) { final String errorMessage = "Could not store file: "+e.getMessage(); logger.log(Level.WARNING, "Could not store file", e); result.put("status", Status.INTERNAL_SERVER_ERROR.name()); result.put("message", errorMessage); - response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(result.toJSONString()).build(); } - return response; + return result.toJSONString(); } // Example test use: diff --git a/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml b/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml index a960807da65..cd5cd12d1b6 100755 --- a/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml +++ b/java/com.sap.sailing.targetplatform.base/features/target-base/feature.xml @@ -128,7 +128,7 @@ id="com.sun.jersey.contribs.jersey-multipart" download-size="0" install-size="0" - version="1.17.1" + version="1.17.0" unpack="false"/> QD~i+i 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 R@UMgkE+gz@?oIfW{{u+7YWM&E diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java index 319dacf98f4..180cf511eb6 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -53,17 +53,21 @@ public class URLFieldWithFileUpload extends HorizontalPanel { removePanel.submit(); } }); + removePanel.add(removeButton); urlTextBox = new TextBox(); add(urlTextBox); FormPanel formPanel = new FormPanel(); + HorizontalPanel formPanelContent = new HorizontalPanel(); + formPanel.add(formPanelContent); formPanel.setAction("/sailingserver/api/v1/file"); formPanel.setEncoding(FormPanel.ENCODING_MULTIPART); formPanel.setMethod(FormPanel.METHOD_POST); fileUploadField = new FileUpload(); + formPanelContent.add(fileUploadField); final InputElement inputElement = fileUploadField.getElement().cast(); inputElement.setName("file"); SubmitButton submitButton = new SubmitButton(stringMessages.upload()); - formPanel.add(submitButton); + formPanelContent.add(submitButton); add(formPanel); formPanel.addSubmitCompleteHandler(new SubmitCompleteHandler() { @Override @@ -77,6 +81,7 @@ public class URLFieldWithFileUpload extends HorizontalPanel { } } }); + add(removePanel); } private void enableDelete() { From 0c4794d41e26ca591252428657ee88d3b9ce1613 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 27 Feb 2015 18:19:13 +0100 Subject: [PATCH 221/264] implement file upload using a plain servlet with Apache multipart suppot, skipping confusing Jersey with FormDataParams that don't work --- .../META-INF/MANIFEST.MF | 3 +- .../WEB-INF/web.xml | 10 ++- .../impl/AbstractFileUploadServlet.java | 47 +++++++++++ .../gateway/impl/FileUploadServlet.java | 79 +++++++++++++++++++ .../jaxrs/api/FileStorageResource.java | 65 +++------------ .../impl/TrackFilesImportServlet.java | 30 ++----- .../adminconsole/URLFieldWithFileUpload.java | 2 +- 7 files changed, 152 insertions(+), 84 deletions(-) create mode 100755 java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java create mode 100755 java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java diff --git a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF index 7798dd00600..e6cb95bf4ac 100644 --- a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF @@ -52,7 +52,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sse.security, 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.apache.shiro.ehcache;bundle-version="1.2.3", + org.jvnet.mimepull;bundle-version="1.9.4" Bundle-ClassPath: . Web-ContextPath: /sailingserver Export-Package: com.sap.sailing.server.gateway, diff --git a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml index 138f4b72d80..fec58ee8e20 100644 --- a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml @@ -254,7 +254,15 @@ TrackFilesImportServlet /trackfiles/import - + + FileUploadServlet + com.sap.sailing.server.gateway.impl.FileUploadServlet + + + FileUploadServlet + /fileupload + + websockettest com.sap.sailing.server.gateway.impl.WebSocketTestServlet diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java new file mode 100755 index 00000000000..7c712f22d94 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java @@ -0,0 +1,47 @@ +package com.sap.sailing.server.gateway.impl; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; + +import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; + +/** + * Abstract servlet handling multipart-mime file upload coming from an upload form + * + * @author Fredrik Teschke + * @author Axel Uhl + * + */ +public abstract class AbstractFileUploadServlet extends AbstractJsonHttpServlet { + public static final String PREFERRED_IMPORTER = "preferredImporter"; + private static final long serialVersionUID = 1120226743039934620L; + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (!ServletFileUpload.isMultipartContent(req)) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + try { + @SuppressWarnings("unchecked") + List items = (List) upload.parseRequest(req); + process(items, req, resp); + } catch (FileUploadException e) { + throw new IOException("Could not parse request"); + } + } + + abstract protected void process(List fileItems, HttpServletRequest req, HttpServletResponse resp) throws IOException; +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java new file mode 100755 index 00000000000..0a2e67926c5 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java @@ -0,0 +1,79 @@ +package com.sap.sailing.server.gateway.impl; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.apache.commons.fileupload.FileItem; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sse.filestorage.InvalidPropertiesException; +import com.sap.sse.filestorage.OperationFailedException; + +/** + * Accepts a multi-part MIME encoded set of files. Returns an array of JSONObjects that each contain + * a "file_uri" value, in the same order in which the parts were sent. + * + * @author Axel Uhl (d043530) + * + */ +public class FileUploadServlet extends AbstractFileUploadServlet { + private static final long serialVersionUID = -9002541098579359029L; + + private static final Logger logger = Logger.getLogger(FileUploadServlet.class.getName()); + + public static final String JSON_FILE_NAME = "file_name"; + + public static final String JSON_FILE_URI = "file_uri"; + + /** + * The maximum size of an image uploaded by a user as a team image, in megabytes (1024*1024 bytes) + */ + private static final int MAX_SIZE_IN_MB = 5; + + @Override + protected void process(List fileItems, HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, IOException { + /** + * Expects the HTTP header {@code Content-Length} to be set. + */ + final JSONArray resultList = new JSONArray(); + for (FileItem fileItem : fileItems) { + JSONObject result = new JSONObject(); + String fileExtension = ""; + String fileType = fileItem.getContentType(); + if (fileType.equals("image/jpeg")) { + fileExtension = ".jpg"; + } else if (fileType.equals("image/png")) { + fileExtension = ".png"; + } + URI fileUri; + try { + if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) { + throw new WebApplicationException(Response.status(Status.BAD_REQUEST) + .entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build()); + } + fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() + .storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize()); + result.put(JSON_FILE_NAME, fileItem.getName()); + result.put(JSON_FILE_URI, fileUri.toString()); + } catch (IOException | OperationFailedException | InvalidPropertiesException e) { + final String errorMessage = "Could not store file: " + e.getMessage(); + logger.log(Level.WARNING, "Could not store file", e); + result.put("status", Status.INTERNAL_SERVER_ERROR.name()); + result.put("message", errorMessage); + } + resultList.add(result); + } + resp.getOutputStream().write(resultList.toJSONString().getBytes("UTF-8")); + } +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java index 0fa71f32ea4..433a0014be2 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java @@ -1,89 +1,42 @@ package com.sap.sailing.server.gateway.jaxrs.api; import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.logging.Level; import java.util.logging.Logger; -import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; -import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.json.simple.JSONObject; +import com.sap.sailing.server.gateway.impl.FileUploadServlet; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.filestorage.InvalidPropertiesException; import com.sap.sse.filestorage.OperationFailedException; -import com.sun.jersey.multipart.FormDataBodyPart; -import com.sun.jersey.multipart.FormDataParam; +/** + * File upload is handled by {@link FileUploadServlet} because Jersey has issues with multipart MIME file posting. This + * resource only handles the DELETE method. + * + * @author Axel Uhl (d043530) + * + */ @Path("/v1/file") public class FileStorageResource extends AbstractSailingServerResource { private static final Logger logger = Logger.getLogger(FileStorageResource.class.getName()); - public static final String JSON_FILE_URI = "file_uri"; - - /** - * The maximum size of an image uploaded by a user as a team image, in megabytes (1024*1024 bytes) - */ - private static final int MAX_SIZE_IN_MB = 5; - - /** - * Expects the HTTP header {@code Content-Length} to be set. - */ - // - // example for testing upload: - // $ curl -v -H "Content-Type:image/jpeg" \ - // --data-binary @ \ - // http://127.0.0.1:8888/sailingserver/api/v1/file - @POST - @Consumes(MediaType.MULTIPART_FORM_DATA) - @Produces("text/html;charset=UTF-8") - public String uploadFile(@FormDataParam("file") InputStream uploadedInputStream, @FormDataParam("file") FormDataBodyPart filePart) { - final JSONObject result = new JSONObject(); - String fileExtension = ""; - MediaType fileType = filePart.getMediaType(); - if (fileType.getType().equals("image")) { - if (fileType.getSubtype().equals("jpeg")) { - fileExtension = ".jpg"; - } else if (fileType.getSubtype().equals("png")) { - fileExtension = ".png"; - } - } - - URI fileUri; - try { - if (filePart.getContentDisposition().getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) { - throw new WebApplicationException(Response.status(Status.BAD_REQUEST) - .entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build()); - } - fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() - .storeFile(uploadedInputStream, fileExtension, filePart.getContentDisposition().getSize()); - result.put(JSON_FILE_URI, fileUri.toString()); - } catch (IOException | OperationFailedException | InvalidPropertiesException e) { - final String errorMessage = "Could not store file: "+e.getMessage(); - logger.log(Level.WARNING, "Could not store file", e); - result.put("status", Status.INTERNAL_SERVER_ERROR.name()); - result.put("message", errorMessage); - } - return result.toJSONString(); - } - // Example test use: // curl -X DELETE http://127.0.0.1:8888/sailingserver/api/v1/file?uri=file:///c:/tmp/c7b821e1-ebab-4a96-a71d-28ac192b3e69.jpg @DELETE @Produces("application/json;charset=UTF-8") - public Response deleteImage(@QueryParam("uri") String uri) { + public Response deleteFile(@QueryParam("uri") String uri) { final JSONObject result = new JSONObject(); Response response; try { diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java index 0c40dfdf8b5..8c58abc1db6 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java @@ -15,15 +15,10 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileItemFactory; -import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.osgi.framework.Filter; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; @@ -35,7 +30,7 @@ import com.sap.sailing.domain.trackimport.FormatNotSupportedException; import com.sap.sailing.domain.trackimport.GPSFixImporter; import com.sap.sailing.domain.trackimport.GPSFixImporter.Callback; import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; +import com.sap.sailing.server.gateway.impl.AbstractFileUploadServlet; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; @@ -57,7 +52,7 @@ import com.sap.sse.common.Util.Pair; * @author Fredrik Teschke * */ -public class TrackFilesImportServlet extends AbstractJsonHttpServlet { +public class TrackFilesImportServlet extends AbstractFileUploadServlet { public static final String PREFERRED_IMPORTER = "preferredImporter"; private static final long serialVersionUID = 1120226743039934620L; private static final Logger logger = Logger.getLogger(TrackFilesImportServlet.class.getName()); @@ -159,26 +154,11 @@ public class TrackFilesImportServlet extends AbstractJsonHttpServlet { return deviceIds; } - @SuppressWarnings("unchecked") @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - if (!ServletFileUpload.isMultipartContent(req)) { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST); - return; - } - - Set> files = new HashSet<>(); - FileItemFactory factory = new DiskFileItemFactory(); - ServletFileUpload upload = new ServletFileUpload(factory); - List items; - try { - items = (List) upload.parseRequest(req); - } catch (FileUploadException e) { - throw new IOException("Could not parse request"); - } - + protected void process(List fileItems, HttpServletRequest req, HttpServletResponse resp) throws IOException { String prefImporterType = null; - for (FileItem item : items) { + List> files = new ArrayList<>(); + for (FileItem item : fileItems) { if (!item.isFormField()) files.add(new Pair(item.getName(), item.getInputStream())); else { diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java index 180cf511eb6..cba5f5512f2 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -59,7 +59,7 @@ public class URLFieldWithFileUpload extends HorizontalPanel { FormPanel formPanel = new FormPanel(); HorizontalPanel formPanelContent = new HorizontalPanel(); formPanel.add(formPanelContent); - formPanel.setAction("/sailingserver/api/v1/file"); + formPanel.setAction("/sailingserver/fileupload"); formPanel.setEncoding(FormPanel.ENCODING_MULTIPART); formPanel.setMethod(FormPanel.METHOD_POST); fileUploadField = new FileUpload(); From 6004065a27a5083294301ebf189ae5e69656d97d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 27 Feb 2015 18:29:19 +0100 Subject: [PATCH 222/264] fixed client for file upload --- .../sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java index cba5f5512f2..995050ebbec 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -3,6 +3,7 @@ package com.sap.sse.gwt.adminconsole; import com.google.gwt.dom.client.InputElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONParser; import com.google.gwt.user.client.Window; @@ -73,9 +74,10 @@ public class URLFieldWithFileUpload extends HorizontalPanel { @Override public void onSubmitComplete(SubmitCompleteEvent event) { String result = event.getResults(); - JSONObject resultJson = (JSONObject) JSONParser.parseLenient(result); - if (resultJson != null && resultJson.get("file_uri") != null) { - uri = resultJson.get("file_uri").toString(); + JSONArray resultJson = (JSONArray) JSONParser.parseLenient(result.replaceFirst("]*>(.*)", "$1")); + if (resultJson != null && resultJson.get(0).isObject().get("file_uri") != null) { + uri = resultJson.get(0).isObject().get("file_uri").toString(); + urlTextBox.setValue(uri); enableDelete(); Window.alert(stringMessages.uploadSuccessful()); } From 56b75890d296477b3a2f0ed83f23c9e9b3c9e788 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 27 Feb 2015 22:01:30 +0100 Subject: [PATCH 223/264] first working version of competitor image upload --- .../domain/common/dto/CompetitorDTO.java | 2 ++ .../domain/common/dto/CompetitorDTOImpl.java | 15 ++++++++- .../common/dto/PreviousCompetitorDTOImpl.java | 6 ++++ .../PreviousSuppressedCompetitorDTOImpl.java | 2 -- .../impl/TransientCompetitorStoreImpl.java | 5 +-- .../test/LeaderboardDTODiffingTest.java | 8 ++--- .../ui/adminconsole/CompetitorEditDialog.java | 3 +- .../sailing/gwt/ui/client/SailingService.java | 2 +- .../gwt/ui/server/SailingServiceImpl.java | 5 +-- .../jaxrs/api/FileStorageResource.java | 10 +++--- .../sse/gwt/adminconsole/StringMessages.java | 3 +- .../adminconsole/URLFieldWithFileUpload.java | 33 +++++++++++++++---- 12 files changed, 67 insertions(+), 27 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java index e6e2d7c78ca..6b6c4ab773b 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java @@ -32,5 +32,7 @@ public interface CompetitorDTO extends Serializable, MappableToDevice { * from the previous version of the enclosing leaderboard. */ CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion); + + String getImageURL(); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java index 6c0cee85da9..edb471564f3 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java @@ -23,11 +23,12 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial private String sailID; private String idAsString; private BoatClassDTO boatClass; + private String imageURL; public CompetitorDTOImpl() {} public CompetitorDTOImpl(String name, Color color, String email, String twoLetterIsoCountryCode, String threeLetterIocCountryCode, - String countryName, String sailID, String idAsString, BoatClassDTO boatClass) { + String countryName, String sailID, String idAsString, String imageURL, BoatClassDTO boatClass) { super(name); this.color = color; this.email = email; @@ -37,6 +38,7 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial this.sailID = sailID; this.idAsString = idAsString; this.boatClass = boatClass; + this.imageURL = imageURL; } @Override @@ -49,6 +51,7 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial result = prime * result + ((color == null) ? 0 : color.hashCode()); result = prime * result + ((email == null) ? 0 : email.hashCode()); result = prime * result + ((threeLetterIocCountryCode == null) ? 0 : threeLetterIocCountryCode.hashCode()); + result = prime * result + ((imageURL == null) ? 0 : imageURL.hashCode()); return result; } @@ -91,6 +94,11 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial return false; } else if (!email.equals(other.email)) return false; + if (imageURL == null) { + if (other.imageURL != null) + return false; + } else if (!imageURL.equals(other.imageURL)) + return false; return true; } @@ -113,6 +121,11 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial public String getSailID() { return sailID; } + + @Override + public String getImageURL() { + return imageURL; + } @Override public String getIdAsString() { diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java index 77ae9538d86..ea7cbc6caec 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java @@ -99,6 +99,12 @@ public class PreviousCompetitorDTOImpl implements CompetitorDTO { " need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization"); } + @Override + public String getImageURL() { + throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+ + " need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization"); + } + @Override public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) { return previousVersion.competitors.get(indexInPreviousCompetitorList); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java index f9d9874b71e..4d1fdbf97d0 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousSuppressedCompetitorDTOImpl.java @@ -24,6 +24,4 @@ public class PreviousSuppressedCompetitorDTOImpl extends PreviousCompetitorDTOIm public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) { return Util.get(previousVersion.getSuppressedCompetitors(), getIndexInPreviousCompetitorList()); } - - } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java index 7c9f8395f45..6caca851d02 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java @@ -232,8 +232,9 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab CountryCode countryCode = nationality == null ? null : nationality.getCountryCode(); competitorDTO = new CompetitorDTOImpl(c.getName(), c.getColor(), c.getEmail(), countryCode == null ? "" : countryCode.getTwoLetterISOCode(), countryCode == null ? "" - : countryCode.getThreeLetterIOCCode(), countryCode == null ? "" : countryCode.getName(), c - .getBoat().getSailID(), c.getId().toString(), new BoatClassDTO(c.getBoat().getBoatClass() + : countryCode.getThreeLetterIOCCode(), countryCode == null ? "" : countryCode.getName(), + c.getBoat().getSailID(), c.getId().toString(), c.getTeam().getImage() == null ? null : c.getTeam().getImage().toString(), + new BoatClassDTO(c.getBoat().getBoatClass() .getName(), c.getBoat().getBoatClass().getDisplayName(), c.getBoat().getBoatClass().getHullLength().getMeters())); weakCompetitorDTOCache.put(c, competitorDTO); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java index 724aac107ce..8127d756a5e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java @@ -233,7 +233,7 @@ public class LeaderboardDTODiffingTest { public void testCompetitorListChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", - new BoatClassDTO("505", 5.05)); + /* imageURL */ null, new BoatClassDTO("505", 5.05)); newVersion.competitors.add(13, somebodyNew); // insert a competitor; this should mess up all others' indexes; check if this works CompetitorDTO wolfgang = getPreviousCompetitorByName("HUNGER +JESS"); newVersion.competitors.remove(wolfgang); @@ -253,7 +253,7 @@ public class LeaderboardDTODiffingTest { newVersion.setSuppressedCompetitors(newSuppressedCompetitors); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", - new BoatClassDTO("505", 5.05)); + /* imageURL */ null, new BoatClassDTO("505", 5.05)); newVersion.setSuppressed(newVersion.competitors.get(13), true); // suppress an existing competitor; compaction should reduce this to a single number only newVersion.setSuppressed(somebodyNew, true); // check that mixed mode with existing and new competitors works as well List newSuppressedCompetitorsBeforeStripping = new ArrayList(); @@ -275,7 +275,7 @@ public class LeaderboardDTODiffingTest { public void testDisplayNameChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", - new BoatClassDTO("505", 5.05)); + /* imageURL */ null, new BoatClassDTO("505", 5.05)); newVersion.competitors.add(somebodyNew); newVersion.competitorDisplayNames = new HashMap(newVersion.competitorDisplayNames); newVersion.competitorDisplayNames.put(newVersion.competitors.get(13), "Humba"); @@ -304,7 +304,7 @@ public class LeaderboardDTODiffingTest { newVersion.setCompetitorsFromBestToWorst(r9, newOrdering); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "GER 1234", "912p09871203987", - new BoatClassDTO("505", 5.05)); + /* imageURL */ null, new BoatClassDTO("505", 5.05)); newVersion.competitors.add(somebodyNew); newOrdering.add(somebodyNew); CompetitorDTO formerRank13 = newOrdering.remove(13); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index 08e3f4992ec..f046f1220f3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -124,6 +124,7 @@ public class CompetitorEditDialog extends DataEntryDialog { } this.sailId = createTextBox(competitorToEdit.getSailID()); imageUrlAndUploadComposite = new URLFieldWithFileUpload(stringMessages); + imageUrlAndUploadComposite.setURL(competitorToEdit.getImageURL()); } @@ -180,7 +181,7 @@ public class CompetitorEditDialog extends DataEntryDialog { /* twoLetterIsoCountryCode */ null, threeLetterIocCountryCode.getValue(threeLetterIocCountryCode.getSelectedIndex()), /* countryName */ null, sailId.getText(), competitorToEdit.getIdAsString(), - boatClass); + imageUrlAndUploadComposite.getURL(), boatClass); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index a66c90200b5..46ca93562b8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -414,7 +414,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS */ Iterable getCompetitorsOfLeaderboard(String leaderboardName, boolean lookInRaceLogs); - CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor); + CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor) throws Exception; void allowCompetitorResetToDefaults(Iterable competitors); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 38ee1c2fdc4..3633c3980b5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -4461,7 +4461,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor) { + public CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor) throws URISyntaxException { Competitor existingCompetitor = getService().getCompetitorStore().getExistingCompetitorByIdAsString(competitor.getIdAsString()); Nationality nationality = (competitor.getThreeLetterIocCountryCode() == null || competitor.getThreeLetterIocCountryCode().isEmpty()) ? null : getBaseDomainFactory().getOrCreateNationality(competitor.getThreeLetterIocCountryCode()); @@ -4479,7 +4479,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S result = getBaseDomainFactory().convertToCompetitorDTO( getService().apply( new UpdateCompetitor(competitor.getIdAsString(), competitor.getName(), competitor - .getColor(), competitor.getEmail(), competitor.getSailID(), nationality, existingCompetitor.getTeam().getImage()))); + .getColor(), competitor.getEmail(), competitor.getSailID(), nationality, + competitor.getImageURL()==null?null:new URI(competitor.getImageURL())))); } return result; } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java index 433a0014be2..2e5d90ed05e 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java @@ -6,7 +6,7 @@ import java.net.URISyntaxException; import java.util.logging.Level; import java.util.logging.Logger; -import javax.ws.rs.DELETE; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; @@ -33,8 +33,8 @@ public class FileStorageResource extends AbstractSailingServerResource { private static final Logger logger = Logger.getLogger(FileStorageResource.class.getName()); // Example test use: - // curl -X DELETE http://127.0.0.1:8888/sailingserver/api/v1/file?uri=file:///c:/tmp/c7b821e1-ebab-4a96-a71d-28ac192b3e69.jpg - @DELETE + // curl -d "uri=file:///c:/tmp/c7b821e1-ebab-4a96-a71d-28ac192b3e69.jpg" http://127.0.0.1:8888/sailingserver/api/v1/file + @POST @Produces("application/json;charset=UTF-8") public Response deleteFile(@QueryParam("uri") String uri) { final JSONObject result = new JSONObject(); @@ -46,8 +46,8 @@ public class FileStorageResource extends AbstractSailingServerResource { response = Response.ok().entity(result.toJSONString()).build(); } catch (NoCorrespondingServiceRegisteredException | OperationFailedException | InvalidPropertiesException | URISyntaxException | IOException e) { - final String errorMessage = "Could not delete file "+uri+": "+e.getMessage(); - logger.log(Level.WARNING, "Could not delete file "+uri, e); + final String errorMessage = "Could not delete file with URI "+uri+": "+e.getMessage(); + logger.log(Level.WARNING, "Could not delete file with URI "+uri, e); result.put("status", Status.BAD_REQUEST.name()); result.put("message", errorMessage); response = Response.status(Status.BAD_REQUEST).entity(result.toJSONString()).build(); diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java index d7986aede36..71126c2c231 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java @@ -1,14 +1,13 @@ package com.sap.sse.gwt.adminconsole; import com.google.gwt.i18n.client.Messages; -import com.google.gwt.json.client.JSONValue; public interface StringMessages extends Messages { String upload(); String remove(); - String removeResult(JSONValue jsonValue, JSONValue jsonValue2); + String removeResult(String status, String message); String uploadSuccessful(); } diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java index 995050ebbec..9db1a36cd76 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -38,11 +38,13 @@ public class URLFieldWithFileUpload extends HorizontalPanel { removePanel.addSubmitCompleteHandler(new SubmitCompleteHandler() { @Override public void onSubmitComplete(SubmitCompleteEvent event) { - final JSONObject result = (JSONObject) JSONParser.parseLenient(event.getResults()); - Window.alert(stringMessages.removeResult(result.get("status"), result.get("message"))); + final JSONObject resultJson = JSONParser.parseLenient(event.getResults().replaceFirst("]*>(.*)", "$1")).isObject(); + Window.alert(stringMessages.removeResult(resultJson.get("status").isString().stringValue(), + resultJson.get("message") == null ? "" : resultJson.get("message").isString().stringValue())); + setURL(""); } }); - removePanel.setMethod("DELETE"); + removePanel.setMethod(FormPanel.METHOD_POST); removeButton = new PushButton(new Image(com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon())); removeButton.setEnabled(false); // the button shall only be enabled as long as we know the URI for removal removeButton.ensureDebugId("RemoveButton"); @@ -52,6 +54,7 @@ public class URLFieldWithFileUpload extends HorizontalPanel { public void onClick(ClickEvent event) { removePanel.setAction("/sailingserver/api/v1/file?uri="+uri); removePanel.submit(); + uri = null; } }); removePanel.add(removeButton); @@ -76,17 +79,33 @@ public class URLFieldWithFileUpload extends HorizontalPanel { String result = event.getResults(); JSONArray resultJson = (JSONArray) JSONParser.parseLenient(result.replaceFirst("]*>(.*)", "$1")); if (resultJson != null && resultJson.get(0).isObject().get("file_uri") != null) { - uri = resultJson.get(0).isObject().get("file_uri").toString(); + uri = resultJson.get(0).isObject().get("file_uri").isString().stringValue(); urlTextBox.setValue(uri); - enableDelete(); + removeButton.setEnabled(true); Window.alert(stringMessages.uploadSuccessful()); } } }); add(removePanel); } + + /** + * Returns null if the trimmed URL field contents are empty + */ + public String getURL() { + final String trimmedUrl = urlTextBox.getValue().trim(); + return trimmedUrl.isEmpty() ? null : trimmedUrl; + } - private void enableDelete() { - removeButton.setEnabled(true); + public void setURL(String imageURL) { + if (imageURL == null) { + urlTextBox.setValue(""); + uri = null; + removeButton.setEnabled(false); + } else { + urlTextBox.setValue(imageURL); + uri = imageURL; + removeButton.setEnabled(true); + } } } From ee9c3a8eb6293109c9b2d3db6435b87cc6101db2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 27 Feb 2015 22:10:31 +0100 Subject: [PATCH 224/264] enable null for NaturalComparator arguments --- .../domain/common/impl/NaturalComparator.java | 4 ++- .../adminconsole/CompetitorTableWrapper.java | 31 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java index 32713ccc178..94a3d24cba4 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/NaturalComparator.java @@ -25,7 +25,9 @@ public class NaturalComparator implements Comparator { * Compare the passed strings in natural order. */ @Override - public int compare(String a, String b) { + public int compare(final String aOrNull, final String bOrNull) { + String a = aOrNull == null ? "" : aOrNull; + String b = bOrNull == null ? "" : bOrNull; if (!caseSensitive){ a = a.toLowerCase(); b = b.toLowerCase(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 2f1acd06de2..4bd40daf9c6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -101,12 +101,40 @@ public class CompetitorTableWrapper> ext } }); + Column imageColumn = new Column(new SafeHtmlCell()) { + @Override + public SafeHtml getValue(CompetitorDTO competitor) { + SafeHtmlBuilder sb = new SafeHtmlBuilder(); + ImageResourceRenderer renderer = new ImageResourceRenderer(); + final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode(); + final ImageResource flagImageResource; + if (twoLetterIsoCountryCode==null || twoLetterIsoCountryCode.isEmpty()) { + flagImageResource = FlagImageResolver.getEmptyFlagImageResource(); + } else { + flagImageResource = FlagImageResolver.getFlagImageResource(twoLetterIsoCountryCode); + } + if (flagImageResource != null) { + sb.append(renderer.render(flagImageResource)); + sb.appendHtmlConstant(" "); + } + sb.appendEscaped(competitor.getSailID()); + return sb.toSafeHtml(); + } + }; + imageColumn.setSortable(true); + competitorColumnListHandler.setComparator(imageColumn, new Comparator() { + private final NaturalComparator comparator = new NaturalComparator(/* case sensitive */ false); + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return comparator.compare(o1.getImageURL(), o2.getImageURL()); + } + }); + TextColumn competitorIdColumn = new TextColumn() { @Override public String getValue(CompetitorDTO competitor) { return competitor.getIdAsString(); } - }; competitorIdColumn.setSortable(true); @@ -119,7 +147,6 @@ public class CompetitorTableWrapper> ext filterField = new LabeledAbstractFilterablePanel(new Label(stringMessages.filterCompetitors()), new ArrayList(), table, dataProvider) { - @Override public Iterable getSearchableStrings(CompetitorDTO t) { List string = new ArrayList(); From c574a1178dbb47bc090558d1ec02f9feed08758b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 27 Feb 2015 23:24:59 +0100 Subject: [PATCH 225/264] don't force application/json as Content-Type for all JAX-RS RESTlets but let them choose themselves --- .../gateway/jaxrs/AccessControlAndEncodingResponseFilter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java index 0031f89ddd0..cc95ae05fcd 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AccessControlAndEncodingResponseFilter.java @@ -15,8 +15,6 @@ public class AccessControlAndEncodingResponseFilter implements ContainerResponse 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"); - httpHeaders.remove("Content-Type"); - httpHeaders.add("Content-Type", "application/json;charset=UTF-8"); return response; } } \ No newline at end of file From c632a3fab109644a8db5dcce88e2f2de31df1f42 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 27 Feb 2015 23:42:47 +0100 Subject: [PATCH 226/264] show uploaded image in competitors table; use file RESTlet so that also file:/// URLs can be displayed properly which the browser wouldn't allow --- .../adminconsole/CompetitorTableWrapper.java | 19 ++++++---------- .../jaxrs/api/CompetitorsResource.java | 1 + .../jaxrs/api/FileStorageResource.java | 22 +++++++++++++++++++ .../gateway/jaxrs/api/RegattasResource.java | 6 +++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 4bd40daf9c6..6ad83b0e296 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -105,19 +105,13 @@ public class CompetitorTableWrapper> ext @Override public SafeHtml getValue(CompetitorDTO competitor) { SafeHtmlBuilder sb = new SafeHtmlBuilder(); - ImageResourceRenderer renderer = new ImageResourceRenderer(); - final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode(); - final ImageResource flagImageResource; - if (twoLetterIsoCountryCode==null || twoLetterIsoCountryCode.isEmpty()) { - flagImageResource = FlagImageResolver.getEmptyFlagImageResource(); - } else { - flagImageResource = FlagImageResolver.getFlagImageResource(twoLetterIsoCountryCode); + if (competitor.getImageURL() != null && !competitor.getImageURL().isEmpty()) { + sb.appendHtmlConstant(""); } - if (flagImageResource != null) { - sb.append(renderer.render(flagImageResource)); - sb.appendHtmlConstant(" "); - } - sb.appendEscaped(competitor.getSailID()); return sb.toSafeHtml(); } }; @@ -163,6 +157,7 @@ public class CompetitorTableWrapper> ext table.addColumn(competitorNameColumn, stringMessages.name()); table.addColumn(boatClassColumn, stringMessages.boatClass()); table.addColumn(displayColorColumn, stringMessages.color()); + table.addColumn(imageColumn, stringMessages.imageURL()); table.addColumn(competitorIdColumn, stringMessages.id()); } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java index e6af41be4fc..7d793b04760 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java @@ -111,6 +111,7 @@ public class CompetitorsResource extends AbstractSailingServerResource { @POST @Consumes({ "image/jpeg", "image/png" }) @Path("{competitor-id}/team/image") + @Produces("application/json;charset=UTF-8") public String setTeamImage(@PathParam("competitor-id") String competitorId, InputStream uploadedInputStream, @HeaderParam("Content-Type") String fileType, @HeaderParam("Content-Length") long sizeInBytes) { diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java index 2e5d90ed05e..bb027ede091 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/FileStorageResource.java @@ -1,16 +1,20 @@ package com.sap.sailing.server.gateway.jaxrs.api; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.util.logging.Level; import java.util.logging.Logger; +import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import org.json.simple.JSONObject; @@ -32,6 +36,24 @@ import com.sap.sse.filestorage.OperationFailedException; public class FileStorageResource extends AbstractSailingServerResource { private static final Logger logger = Logger.getLogger(FileStorageResource.class.getName()); + @GET + public Response getFile(@QueryParam("uri") String uri) { + Response response; + try { + InputStream inputStream = new URL(uri).openStream(); + ResponseBuilder responseBuilder = Response.ok().entity(inputStream); + if (uri.toLowerCase().endsWith(".jpg")) { + responseBuilder.header("Content-Type", "image/jpeg"); + } else if (uri.toLowerCase().endsWith(".png")) { + responseBuilder.header("Content-Type", "image/png"); + } + response = responseBuilder.build(); + } catch (IOException ioe) { + response = Response.status(Status.BAD_REQUEST).entity(ioe.getMessage()).build(); + } + return response; + } + // Example test use: // curl -d "uri=file:///c:/tmp/c7b821e1-ebab-4a96-a71d-28ac192b3e69.jpg" http://127.0.0.1:8888/sailingserver/api/v1/file @POST diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index 751720ade13..f1ae8aa36fc 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -7,6 +7,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; @@ -65,6 +67,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; @Path("/v1/regattas") public class RegattasResource extends AbstractSailingServerResource { + private static final Logger logger = Logger.getLogger(RegattasResource.class.getName()); private Response getBadRegattaErrorResponse(String regattaName) { return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build(); @@ -651,6 +654,9 @@ public class RegattasResource extends AbstractSailingServerResource { jsonCompetitorInLeg.put("jibes", numberOfJibes); jsonCompetitorInLeg.put("penaltyCircles", numberOfPenaltyCircles); } catch (NoWindException e) { + logger.log(Level.FINE, + "No wind information while trying to determing maneuvers for competitor " + + competitor.getName(), e); } TimePoint startTime = trackedLegOfCompetitor.getStartTime(); From 8fd6aa1629a2dc2b7e233478199e8a9f58ff94a7 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 2 Mar 2015 12:03:06 +0100 Subject: [PATCH 227/264] fixed typo in constant name --- .../gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java index b077beda8e0..fe012ee543c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java @@ -39,7 +39,7 @@ import com.sap.sse.gwt.client.player.Timer; * */ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay { - public static final String LODA_WIND_STREAMLET_DATA_CATEGORY = "loadWindStreamletData"; + public static final String LOAD_WIND_STREAMLET_DATA_CATEGORY = "loadWindStreamletData"; private static final int animationIntervalMillis = 40; private static final long RESOLUTION_IN_MILLIS = 5000; private static final int WIND_FETCH_INTERVAL_IN_MILLIS = 10000; @@ -176,7 +176,7 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay { } GetWindInfoAction getWind = new GetWindInfoAction(sailingService, raceIdentifier, timeOfLastFixOfSource, /* endOfTime */ null, RESOLUTION_IN_MILLIS, windSourceTypeNames, /* onlyUpToNewestEvent */ true); - asyncActionsExecutor.execute(getWind, LODA_WIND_STREAMLET_DATA_CATEGORY, + asyncActionsExecutor.execute(getWind, LOAD_WIND_STREAMLET_DATA_CATEGORY, new MarkedAsyncCallback<>(new AsyncCallback() { @Override public void onFailure(Throwable caught) { From 73693e22d7b2be89ccc2b9f676a96647bb98e9bb Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 2 Mar 2015 12:03:18 +0100 Subject: [PATCH 228/264] use startOfRace when no startOfTracking is set as a default for wind time range start --- .../java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index efe9616b827..bcaae8e007e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -1405,7 +1405,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); WindInfoForRaceDTO result = null; if (trackedRace != null) { - TimePoint fromTimePoint = from == null ? trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from); + TimePoint fromTimePoint = from == null ? trackedRace.getStartOfTracking() == null ? trackedRace + .getStartOfRace() : trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from); TimePoint toTimePoint = to == null ? trackedRace.getEndOfRace() == null ? MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : trackedRace.getEndOfRace() : new MillisecondsTimePoint(to); if (fromTimePoint != null && toTimePoint != null) { From 62c05925320185d5ca685b2c9fe05d6b4011dcf9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 2 Mar 2015 12:03:43 +0100 Subject: [PATCH 229/264] fixed initialization order, avoiding a race condition leading to an NPE --- .../java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java index 1fdf83b3aeb..c49b3b8725e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java @@ -88,6 +88,7 @@ public class QuickRanksLiveCache { } }, getClass().getName()); fromRefToRaceIdentifier = new HashMap<>(); + referencesToGarbageCollectedRaces = new ReferenceQueue<>(); new Thread("QuickRanksLiveCache garbage collector") { @Override public void run() { @@ -107,7 +108,6 @@ public class QuickRanksLiveCache { logger.info("Received stop in QuickRanksLiveCache garbage collector; terminating"); } }.start(); - referencesToGarbageCollectedRaces = new ReferenceQueue<>(); } private void remove(RegattaAndRaceIdentifier raceIdentifier) { From 9cc1978c743db8f5c38809bd848469b27bd8c332 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Mon, 2 Mar 2015 15:58:07 +0100 Subject: [PATCH 230/264] Fixed competitor name evaluation - still not stable --- .../gwt/ui/leaderboard/LeaderboardPanel.java | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index 9961829d75b..b2bac47247d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -2824,36 +2824,40 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS final int nextColumnIndex; int columnCounter = 0; int leaderboardColumnCount = getLeaderboardTable().getColumnCount(); - if (isShowCompetitorSailId() && competitorSailIdColumnIndex == -1) { - competitorSailIdColumnIndex = sailIdColumnIndex; - if (leaderboardColumnCount > 1) { - insertColumn(competitorSailIdColumnIndex, new SailIDColumn(new CompetitorFetcher() { - @Override - public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { - return t.competitor; - } - })); - } else { - addColumn(new SailIDColumn(new CompetitorFetcher() { - @Override - public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { - return t.competitor; - } - })); + if (isShowCompetitorSailId()) { + if (competitorSailIdColumnIndex == -1) { + competitorSailIdColumnIndex = sailIdColumnIndex; + if (leaderboardColumnCount > 1) { + insertColumn(competitorSailIdColumnIndex, new SailIDColumn(new CompetitorFetcher() { + @Override + public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { + return t.competitor; + } + })); + } else { + addColumn(new SailIDColumn(new CompetitorFetcher() { + @Override + public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { + return t.competitor; + } + })); + } } columnCounter++; } else if (!isShowCompetitorSailId() && competitorSailIdColumnIndex != -1) { removeColumn(competitorSailIdColumnIndex); competitorSailIdColumnIndex = -1; } - if (isShowCompetitorFullName() && competitorFullNameColumnIndex == -1) { - if (leaderboardColumnCount > 1) { - insertColumn(sailIdColumnIndex+columnCounter, createCompetitorColumn()); - } else { - addColumn(createCompetitorColumn()); + if (isShowCompetitorFullName()) { + if (competitorFullNameColumnIndex == -1) { + if (leaderboardColumnCount > 1) { + insertColumn(sailIdColumnIndex+columnCounter, createCompetitorColumn()); + } else { + addColumn(createCompetitorColumn()); + } + competitorFullNameColumnIndex = sailIdColumnIndex+columnCounter; } columnCounter++; - competitorFullNameColumnIndex = sailIdColumnIndex+columnCounter; } else if (!isShowCompetitorFullName() && competitorFullNameColumnIndex != -1) { removeColumn(competitorFullNameColumnIndex); competitorFullNameColumnIndex = -1; From 5206c795487354b88293158cbcf04db2745cd2cf Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Mon, 2 Mar 2015 16:18:44 +0100 Subject: [PATCH 231/264] Fixed some stuff after new datamining -> polar mining merge --- .../sailing/polars/mining/PolarDataMiner.java | 21 ++++++++++++------- .../SailingServer (No Proxy).launch | 2 +- ...ailingServer (No Proxy, Cached MTB).launch | 2 +- ...ingServer (No Proxy, Jetty on 8889).launch | 4 ++-- ...o Proxy, Jetty on 8889, Cached MTB).launch | 6 +++--- ...ingServer (No Proxy, Jetty on 8890).launch | 4 ++-- ...ver (No Proxy, Remote Debug SAP VM).launch | 6 +++--- ...ailingServer (No Proxy, winddbTest).launch | 4 ++-- .../SailingServer (Proxy).launch | 2 +- ...ailingServer (Proxy, Jetty on 8889).launch | 4 ++-- ...Server (Proxy, Remote Debug SAP VM).launch | 6 +++--- ...Proxy, SwissTiming Live Simulation).launch | 4 ++-- .../SailingServer (Proxy, winddbTest).launch | 4 ++-- 13 files changed, 37 insertions(+), 32 deletions(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index 328ee5b1ac4..5c489aa66ac 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -97,32 +97,37 @@ public class PolarDataMiner { movingAverageGrouperResultReceivers.add(movingAverageProcessor); Collection, ParameterProvider>> dimensionsWithParameterProviders = new ArrayList<>(); - for (Function function : PolarDataDimensionCollectionFactory.getClusterKeyDimensions()) { + for (Function function : PolarDataDimensionCollectionFactory.getMovingAverageClusterKeyDimensions()) { dimensionsWithParameterProviders.add(new Pair, ParameterProvider>(function, ParameterProvider.NULL)); } Processor> movingAverageGroupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( - GPSFixMovingWithPolarContext.class, executor, grouperResultReceivers, dimensionsWithParameterProviders); + GPSFixMovingWithPolarContext.class, executor, movingAverageGrouperResultReceivers, dimensionsWithParameterProviders); cubicRegressionPerCourseProcessor = new CubicRegressionPerCourseProcessor(); Collection, ?>> regressionPerCourseGrouperResultReceivers = new ArrayList, ?>>(); regressionPerCourseGrouperResultReceivers.add(cubicRegressionPerCourseProcessor); - Collection> cubicRegressionPerCourseDimensions = PolarDataDimensionCollectionFactory.getCubicRegressionPerCourseClusterKeyDimensions(); - + Collection, ParameterProvider>> dimensionsWithParameterProvidersForCubicRegression = new ArrayList<>(); + for (Function function : PolarDataDimensionCollectionFactory.getCubicRegressionPerCourseClusterKeyDimensions()) { + dimensionsWithParameterProvidersForCubicRegression.add(new Pair, ParameterProvider>(function, ParameterProvider.NULL)); + } Processor> cubicRegressionPerCourseGroupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( - GPSFixMovingWithPolarContext.class, executor, regressionPerCourseGrouperResultReceivers, cubicRegressionPerCourseDimensions); + GPSFixMovingWithPolarContext.class, executor, regressionPerCourseGrouperResultReceivers, dimensionsWithParameterProvidersForCubicRegression); ClusterGroup angleClusterGroup = createAngleClusterGroup(); speedRegressionPerAngleClusterProcessor = new SpeedRegressionPerAngleClusterProcessor(angleClusterGroup); Collection, ?>> regressionPerAngleClusterGrouperResultReceivers = new ArrayList, ?>>(); regressionPerAngleClusterGrouperResultReceivers.add(speedRegressionPerAngleClusterProcessor); - Collection> regressionPerAngleClusterDimensions = PolarDataDimensionCollectionFactory.getSpeedRegressionPerAngleClusterClusterKeyDimensions(); - + Collection, ParameterProvider>> dimensionsWithParameterProvidersForRegressionPerAngleCluster = new ArrayList<>(); + for (Function function : PolarDataDimensionCollectionFactory.getSpeedRegressionPerAngleClusterClusterKeyDimensions()) { + dimensionsWithParameterProvidersForRegressionPerAngleCluster.add(new Pair, ParameterProvider>(function, ParameterProvider.NULL)); + } + Processor> regressionPerAngleClusterGroupingProcessor = new ParallelMultiDimensionsValueNestingGroupingProcessor( - GPSFixMovingWithPolarContext.class, executor, regressionPerAngleClusterGrouperResultReceivers, regressionPerAngleClusterDimensions); + GPSFixMovingWithPolarContext.class, executor, regressionPerAngleClusterGrouperResultReceivers, dimensionsWithParameterProvidersForRegressionPerAngleCluster); Collection> filteringResultReceivers = new ArrayList<>(); diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index dbda9a5f9de..cd7c44d9153 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -34,5 +34,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch index dc792e3506f..aca684a9833 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch @@ -33,5 +33,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 69a5e66fe14..4d4cb9afe18 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -23,10 +23,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index 68c47acb5b8..7db9e27781e 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -16,17 +16,17 @@ - + - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index 877c62f65a7..16916c8eb46 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -22,10 +22,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index 04690b503fa..06210082415 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -15,16 +15,16 @@ - + - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch index c697f884a12..c8df9bb36ef 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -29,9 +29,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 928d278fb4c..ed447bda52d 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -34,6 +34,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 9b48f3ec5b0..e8f5ad3d188 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -23,10 +23,10 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index 3f70919f0bc..b7ab30bd3dd 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -16,15 +16,15 @@ - + - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index 5d64c2adeb8..7bb1cdd318d 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -22,9 +22,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index dc1d3ebe3e8..a9640ab807e 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -24,10 +24,10 @@ - + - + From 7e56a63744ad04f7c32762ecab3bca076efc00f2 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 2 Mar 2015 16:39:59 +0100 Subject: [PATCH 232/264] a script that may be used to compare the contents of different servers; not yet complete --- java/target/compareServers | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100755 java/target/compareServers diff --git a/java/target/compareServers b/java/target/compareServers new file mode 100755 index 00000000000..1982a3f71e6 --- /dev/null +++ b/java/target/compareServers @@ -0,0 +1,27 @@ +#!/bin/sh + +# Given the base URLs of two SAP Sailing servers, obtains their /sailingserver/api/v1/leaderboardgroups +# service output, sorts it, compares them, and if equal, obtains all corresponding leaderboardgroups/* +# documents and compares those in turn. + +OLDSERVER=$1 +NEWSERVER=$2 + +wget -O - ${OLDSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | sort >leaderboardgroups.old.sed +wget -O - ${NEWSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | sort >leaderboardgroups.new.sed + +diff leaderboardgroups.old.sed leaderboardgroups.new.sed +if [ "$?" = "0" ]; then + cat leaderboardgroups.old.sed | while read i; do + wGET -o "$I.Old.json" "${OLDSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" + wget -O "$i.new.json" "${NEWSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" + diff "$i.old.json" "$i.new.json" + DIFFRESULT=$? + if [ "$DIFFRESULT?' != "0" ]; then + echo Difference detected in leaderboard group $i; exiting + exit $DIFFRESULT + fi + done +fi + + From 427adbb8be2c8935336c9f798acbd36868f91bf5 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 2 Mar 2015 18:24:37 +0100 Subject: [PATCH 233/264] compare JSON in pretty-printed way, using Python's json.tool --- java/target/compareServers | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/java/target/compareServers b/java/target/compareServers index 1982a3f71e6..0ed959b304a 100755 --- a/java/target/compareServers +++ b/java/target/compareServers @@ -7,18 +7,18 @@ OLDSERVER=$1 NEWSERVER=$2 -wget -O - ${OLDSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | sort >leaderboardgroups.old.sed -wget -O - ${NEWSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | sort >leaderboardgroups.new.sed +wget -O - ${OLDSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | grep -v "^$" | sort >leaderboardgroups.old.sed +wget -O - ${NEWSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | grep -v "^$" | sort >leaderboardgroups.new.sed diff leaderboardgroups.old.sed leaderboardgroups.new.sed if [ "$?" = "0" ]; then cat leaderboardgroups.old.sed | while read i; do - wGET -o "$I.Old.json" "${OLDSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" - wget -O "$i.new.json" "${NEWSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" - diff "$i.old.json" "$i.new.json" + wget -O - "${OLDSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" | python -m json.tool >"$i.old.json" + wget -O - "${NEWSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" | python -m json.tool >"$i.new.json" + diff -b -B "$i.old.json" "$i.new.json" DIFFRESULT=$? - if [ "$DIFFRESULT?' != "0" ]; then - echo Difference detected in leaderboard group $i; exiting + if [ "$DIFFRESULT" != "0" ]; then + echo "Difference detected in leaderboard group $i; exiting" exit $DIFFRESULT fi done From cecc2269f68bb943537feb7672d085b80f3b3457 Mon Sep 17 00:00:00 2001 From: Frederik Petersen Date: Mon, 2 Mar 2015 18:27:28 +0100 Subject: [PATCH 234/264] Introduce max capacity for executor quere in polar mining --- .../sap/sailing/polars/mining/PolarDataMiner.java | 2 +- .../AbstractPartitioningParallelProcessor.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index 5c489aa66ac..24da4cc02e6 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -54,7 +54,7 @@ public class PolarDataMiner { private static ThreadPoolExecutor createExecutor() { return new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, 60, TimeUnit.SECONDS, - new LinkedBlockingQueue()); + new LinkedBlockingQueue(100)); } diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractPartitioningParallelProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractPartitioningParallelProcessor.java index 8b08c15f117..0b25f006f29 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractPartitioningParallelProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/AbstractPartitioningParallelProcessor.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; @@ -57,7 +58,16 @@ public abstract class AbstractPartitioningParallelProcessor Date: Mon, 2 Mar 2015 21:06:49 +0100 Subject: [PATCH 235/264] ignore case-only changes when comparing leaderboard groups for their loaded-ness state --- java/target/compareServers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/target/compareServers b/java/target/compareServers index 0ed959b304a..476f70b470a 100755 --- a/java/target/compareServers +++ b/java/target/compareServers @@ -15,7 +15,7 @@ if [ "$?" = "0" ]; then cat leaderboardgroups.old.sed | while read i; do wget -O - "${OLDSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" | python -m json.tool >"$i.old.json" wget -O - "${NEWSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" | python -m json.tool >"$i.new.json" - diff -b -B "$i.old.json" "$i.new.json" + diff -b -i -B "$i.old.json" "$i.new.json" DIFFRESULT=$? if [ "$DIFFRESULT" != "0" ]; then echo "Difference detected in leaderboard group $i; exiting" From 9a2b4b1e6541138cc64ed519d62818b4f890e254 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 2 Mar 2015 21:22:17 +0100 Subject: [PATCH 236/264] quick fix for bug 2671; is a better solution needed for the case where the mark position is not (yet) known? --- .../CandidateFinder.java | 9 +- ...WT Remote Profiling (Run-Mode only).launch | 120 +++++++++--------- .../Security UI sdm.launch | 8 +- 3 files changed, 70 insertions(+), 67 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java index 75814996290..eceabaef3fd 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java @@ -368,8 +368,13 @@ public class CandidateFinder implements AbstractCandidateFinder { boolean result = true; if (passingInstructions.get(w) == PassingInstruction.Port || passingInstructions.get(w) == PassingInstruction.Starboard || passingInstructions.get(w) == PassingInstruction.FixedBearing) { - result = p.crossTrackError(race.getOrCreateTrack(w.getMarks().iterator().next()).getEstimatedPosition(t, true), - race.getCrossingBearing(w, t).add(new DegreeBearingImpl(90))).getMeters() < 0; + final Position estimatedMarkPosition = race.getOrCreateTrack(w.getMarks().iterator().next()).getEstimatedPosition(t, true); + if (estimatedMarkPosition != null) { + result = p.crossTrackError(estimatedMarkPosition, + race.getCrossingBearing(w, t).add(new DegreeBearingImpl(90))).getMeters() < 0; + } else { + // TODO + } } else if (passingInstructions.get(w) == PassingInstruction.Gate) { // TODO } else if (passingInstructions.get(w) == PassingInstruction.Offset) { diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch index 96766428576..7c55b6a1b87 100644 --- a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch @@ -1,60 +1,60 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch index 7750b7dbad8..dede2cd8d60 100755 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -5,12 +5,10 @@ - - - + - + @@ -43,7 +41,7 @@ - + From ff44bd90f3b4954a88ebd0c5fa1fd349413bd687 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 2 Mar 2015 22:41:23 +0100 Subject: [PATCH 237/264] tuning the compareServers script --- java/target/compareServers | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/java/target/compareServers b/java/target/compareServers index 476f70b470a..5c716b3e23f 100755 --- a/java/target/compareServers +++ b/java/target/compareServers @@ -4,23 +4,47 @@ # service output, sorts it, compares them, and if equal, obtains all corresponding leaderboardgroups/* # documents and compares those in turn. +options='c' +while getopts $options option +do + case $option in + c) continue="1";; + \?) echo "Invalid option" + exit 4;; + esac +done +shift $((OPTIND-1)) + OLDSERVER=$1 NEWSERVER=$2 +OLDGROUPS=leaderboardgroups.old.sed +NEWGROUPS=leaderboardgroups.new.sed -wget -O - ${OLDSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | grep -v "^$" | sort >leaderboardgroups.old.sed -wget -O - ${NEWSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | grep -v "^$" | sort >leaderboardgroups.new.sed +# If we continue, don't download again +if [ "$continue" != "1" ]; then + wget -O - ${OLDSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | grep -v "^$" | sort >$OLDGROUPS + wget -O - ${NEWSERVER}/sailingserver/api/v1/leaderboardgroups | sed -e 's/,/\n/g' -e 's/\[/\n/' -e 's/\]/\n/' -e 's/\"//g' | grep -v "^$" | sort >$NEWGROUPS +fi diff leaderboardgroups.old.sed leaderboardgroups.new.sed if [ "$?" = "0" ]; then - cat leaderboardgroups.old.sed | while read i; do - wget -O - "${OLDSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" | python -m json.tool >"$i.old.json" - wget -O - "${NEWSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $i | sed -e 's/ /%20/g'`" | python -m json.tool >"$i.new.json" - diff -b -i -B "$i.old.json" "$i.new.json" + OLD_LB=`head -n 1 $OLDGROUPS` + NEW_LB=`head -n 1 $NEWGROUPS` + while [ "$OLD_LB" != "" ]; do + wget -O - "${OLDSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $OLD_LB | sed -e 's/ /%20/g'`" | sed -e 's/470W/470/g' -e 's/\<505\>/5O5/g' -e 's/Laser Int\./Laser/g' | python -m json.tool | grep -v timepoint >"${OLD_LB}.old.json" + wget -O - "${NEWSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $NEW_LB | sed -e 's/ /%20/g'`" | sed -e 's/470W/470/g' -e 's/\<505\>/5O5/g' -e 's/Laser Int\./Laser/g' | python -m json.tool | grep -v timepoint >"${NEW_LB}.new.json" + diff -b -i -B "${OLD_LB}.old.json" "${NEW_LB}.new.json" DIFFRESULT=$? if [ "$DIFFRESULT" != "0" ]; then - echo "Difference detected in leaderboard group $i; exiting" + echo "Difference detected in leaderboard group $OLD_LB vs. $NEW_LB; exiting" exit $DIFFRESULT + else + # no diff; remove the leaderboards successfully compared from both leaderboardgroups lists in case a later -c invocation occurs + sed -i -e '1d' $OLDGROUPS + sed -i -e '1d' $NEWGROUPS fi + OLD_LB=`head -n 1 $OLDGROUPS` + NEW_LB=`head -n 1 $NEWGROUPS` done fi From 175b8c6d400e413dee5a9733d92db349fe0db0b1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 2 Mar 2015 23:06:18 +0100 Subject: [PATCH 238/264] fixed NPE check in convertToRegattaDTO --- .../java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 503f2515e83..7bb02ffe285 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -768,7 +768,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S regattaDTO.races = convertToRaceDTOs(regatta); regattaDTO.series = convertToSeriesDTOs(regatta); regattaDTO.startDate = regatta.getStartDate() != null ? regatta.getStartDate().asDate() : null; - regattaDTO.endDate = regatta.getStartDate() != null ? regatta.getEndDate().asDate() : null; + regattaDTO.endDate = regatta.getEndDate() != null ? regatta.getEndDate().asDate() : null; BoatClass boatClass = regatta.getBoatClass(); if (boatClass != null) { regattaDTO.boatClass = new BoatClassDTO(boatClass.getName(), boatClass.getDisplayName(), boatClass.getHullLength().getMeters()); From 310dc7cc4734b2f459717bd736bd0587aee46397 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 2 Mar 2015 23:21:00 +0100 Subject: [PATCH 239/264] fixing bug2670: using selection checkbox in leaderboard group config panel --- .../LeaderboardGroupConfigPanel.java | 37 ++++++++++++++----- .../controls/SelectionCheckboxColumn.java | 7 +++- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java index 3f8684ea062..7854bb7ce6b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java @@ -44,6 +44,7 @@ import com.sap.sailing.gwt.ui.client.LeaderboardsRefresher; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; @@ -63,6 +64,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements SafeHtml cell(String url, String displayName); } + private static AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); + private static final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); private VerticalPanel mainPanel; @@ -186,6 +189,16 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements ListHandler leaderboardsListHandler = new ListHandler(leaderboardsProvider.getList()); leaderboardsTable = new CellTable(10000, tableRes); leaderboardsTable.ensureDebugId("LeaderboardsCellTable"); + SelectionCheckboxColumn leaderboardTableSelectionColumn = + new SelectionCheckboxColumn( + tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { + @Override + protected ListDataProvider getListDataProvider() { + return leaderboardsProvider; + } + }; + leaderboardsSelectionModel = leaderboardTableSelectionColumn.getSelectionModel(); + leaderboardsTable.setSelectionModel(leaderboardsSelectionModel, leaderboardTableSelectionColumn.getSelectionManager()); leaderboardsFilterablePanel = new LabeledAbstractFilterablePanel(filterLeaderboardsLabel, availableLeaderboards, leaderboardsTable, leaderboardsProvider) { @Override public Iterable getSearchableStrings(StrippedLeaderboardDTO t) { @@ -240,10 +253,10 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements leaderboardsTable.setWidth("100%"); leaderboardsTable.addColumnSortHandler(leaderboardsListHandler); + leaderboardsTable.addColumn(leaderboardTableSelectionColumn); leaderboardsTable.addColumn(leaderboardsNameColumn, stringMessages.leaderboardName()); leaderboardsTable.addColumn(leaderboardsRacesColumn, stringMessages.races()); - leaderboardsSelectionModel = new MultiSelectionModel(); leaderboardsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { @@ -251,11 +264,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements moveToGroupButton.setEnabled(selectedLeaderboards != null && selectedLeaderboards.size() > 0); } }); - leaderboardsTable.setSelectionModel(leaderboardsSelectionModel); - leaderboardsProvider.addDataDisplay(leaderboardsTable); leaderboardsPanel.add(leaderboardsTable); - return leaderboardsCaptionPanel; } @@ -350,11 +360,23 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements groupDetailsTable = new CellTable(10000, tableRes); groupDetailsTable.ensureDebugId("LeaderboardGroupsCellTable"); + groupDetailsProvider = new ListDataProvider(); + groupDetailsProvider.addDataDisplay(groupDetailsTable); + SelectionCheckboxColumn groupDetailsTableSelectionColumn = + new SelectionCheckboxColumn( + tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { + @Override + protected ListDataProvider getListDataProvider() { + return groupDetailsProvider; + } + }; + groupDetailsTable.setWidth("100%"); + groupDetailsTable.addColumn(groupDetailsTableSelectionColumn); groupDetailsTable.addColumn(groupDetailsNameColumn, stringMessages.leaderboardName()); groupDetailsTable.addColumn(groupDetailsRacesColumn, stringMessages.races()); - groupDetailsSelectionModel = new MultiSelectionModel(); + groupDetailsSelectionModel = groupDetailsTableSelectionColumn.getSelectionModel(); groupDetailsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { @@ -364,10 +386,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements leaderboardUpButton.setEnabled(selectedLeaderboardsInGroup != null && selectedLeaderboardsInGroup.size() == 1); } }); - groupDetailsTable.setSelectionModel(groupDetailsSelectionModel); - - groupDetailsProvider = new ListDataProvider(); - groupDetailsProvider.addDataDisplay(groupDetailsTable); + groupDetailsTable.setSelectionModel(groupDetailsSelectionModel, groupDetailsTableSelectionColumn.getSelectionManager()); groupDetailsPanel.add(groupDetailsTable); //Create details functionality diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java index 95c93ef6037..d328719ca5e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java @@ -130,10 +130,13 @@ public abstract class SelectionCheckboxColumn extends AbstractSortableColumnW } /** - * Subclass implementation has to tell whether the checkbox is selected or deselected for row + * The checkbox value is determined by by selection state of row, using the + * {@link #getSelectionModel() selection model}. */ @Override - public abstract Boolean getValue(T row); + public Boolean getValue(T row) { + return getSelectionModel().isSelected(row); + } /** * The default header display is a check mark. Subclasses may redefine this. From aef326646a28fed16e65a782b0db32e3579e1149 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 00:28:23 +0100 Subject: [PATCH 240/264] made server comparison based on leaderboard groups web service more lenient --- java/target/compareServers | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/target/compareServers b/java/target/compareServers index 5c716b3e23f..1df56a83ed2 100755 --- a/java/target/compareServers +++ b/java/target/compareServers @@ -31,8 +31,8 @@ if [ "$?" = "0" ]; then OLD_LB=`head -n 1 $OLDGROUPS` NEW_LB=`head -n 1 $NEWGROUPS` while [ "$OLD_LB" != "" ]; do - wget -O - "${OLDSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $OLD_LB | sed -e 's/ /%20/g'`" | sed -e 's/470W/470/g' -e 's/\<505\>/5O5/g' -e 's/Laser Int\./Laser/g' | python -m json.tool | grep -v timepoint >"${OLD_LB}.old.json" - wget -O - "${NEWSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $NEW_LB | sed -e 's/ /%20/g'`" | sed -e 's/470W/470/g' -e 's/\<505\>/5O5/g' -e 's/Laser Int\./Laser/g' | python -m json.tool | grep -v timepoint >"${NEW_LB}.new.json" + wget -O - "${OLDSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $OLD_LB | sed -e 's/ /%20/g'`" | sed -e 's/\b470W\b/470/g' -e 's/\b470M\b/470/g' -e 's/\<505\>/5O5/g' -e 's/Laser Int\./Laser/g' | python -m json.tool | grep -v timepoint >"${OLD_LB}.old.json" + wget -O - "${NEWSERVER}/sailingserver/api/v1/leaderboardgroups/`echo $NEW_LB | sed -e 's/ /%20/g'`" | sed -e 's/\b470W\b/470/g' -e 's/\b470M\b/470/g' -e 's/\<505\>/5O5/g' -e 's/Laser Int\./Laser/g' | python -m json.tool | grep -v timepoint >"${NEW_LB}.new.json" diff -b -i -B "${OLD_LB}.old.json" "${NEW_LB}.new.json" DIFFRESULT=$? if [ "$DIFFRESULT" != "0" ]; then From 791f985d13762e1ada76fe749bf91df2f7c72063 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 09:52:42 +0100 Subject: [PATCH 241/264] added missing header for checkbox column --- .../gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java index 7854bb7ce6b..c8dfe53f38d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java @@ -253,7 +253,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements leaderboardsTable.setWidth("100%"); leaderboardsTable.addColumnSortHandler(leaderboardsListHandler); - leaderboardsTable.addColumn(leaderboardTableSelectionColumn); + leaderboardsTable.addColumn(leaderboardTableSelectionColumn, leaderboardTableSelectionColumn.getHeader()); leaderboardsTable.addColumn(leaderboardsNameColumn, stringMessages.leaderboardName()); leaderboardsTable.addColumn(leaderboardsRacesColumn, stringMessages.races()); @@ -372,7 +372,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements }; groupDetailsTable.setWidth("100%"); - groupDetailsTable.addColumn(groupDetailsTableSelectionColumn); + groupDetailsTable.addColumn(groupDetailsTableSelectionColumn, groupDetailsTableSelectionColumn.getHeader()); groupDetailsTable.addColumn(groupDetailsNameColumn, stringMessages.leaderboardName()); groupDetailsTable.addColumn(groupDetailsRacesColumn, stringMessages.races()); From f317e70bc46838dac6c3a419b193994d9bc6041a Mon Sep 17 00:00:00 2001 From: fmittag Date: Tue, 3 Mar 2015 11:16:31 +0100 Subject: [PATCH 242/264] little UI adjustments --- .../sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index f046f1220f3..75d41369a88 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -45,7 +45,7 @@ public class CompetitorEditDialog extends DataEntryDialog { public CompetitorEditDialog(final StringMessages stringMessages, CompetitorDTO competitorToEdit, DialogCallback callback) { - super(stringMessages.editCompetitor(), stringMessages.editCompetitor(), stringMessages.ok(), stringMessages + super(stringMessages.editCompetitor(), null, stringMessages.ok(), stringMessages .cancel(), new Validator() { @Override public String getErrorMessage(CompetitorDTO valueToValidate) { @@ -93,7 +93,6 @@ public class CompetitorEditDialog extends DataEntryDialog { this.name = createTextBox(competitorToEdit.getName()); this.email = createTextBox(competitorToEdit.getEmail()); - this.email.getElement().setPropertyString("placeholder", stringMessages.pleaseEnterAnEmail()); this.displayColorTextBox = createTextBox(competitorToEdit.getColor() == null ? "" : competitorToEdit.getColor().getAsHtml()); this.threeLetterIocCountryCode = createListBox(/* isMultipleSelect */ false); CountryCodeFactory ccf = CountryCodeFactory.INSTANCE; From 4b79441c190e2a4b537ac20fe7394ac7d4778cb6 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Tue, 3 Mar 2015 12:51:47 +0100 Subject: [PATCH 243/264] Working version where leaderboard columns sailid and competitor name can be toggled --- .../SailingDashboardsGWT sdm.launch | 80 +++++----- ...WT Remote Profiling (Run-Mode only).launch | 120 +++++++------- .../SailingGWT sdm.launch | 150 +++++++++--------- java/com.sap.sailing.gwt.ui/SailingGWT.launch | 146 ++++++++--------- .../client/place/regatta/RegattaActivity.java | 3 + .../client/place/series/SeriesActivity.java | 3 + .../ui/leaderboard/LeaderboardEntryPoint.java | 3 + .../gwt/ui/leaderboard/LeaderboardPanel.java | 74 ++++----- ...ingServer (No Proxy, Jetty on 8889).launch | 64 ++++---- .../Security UI sdm.launch | 94 +++++------ 10 files changed, 368 insertions(+), 369 deletions(-) diff --git a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch index b0e93eef659..931c405bff9 100644 --- a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch +++ b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch @@ -1,40 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch index 7c55b6a1b87..ad65be7f6f2 100644 --- a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch @@ -1,60 +1,60 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index 81118d7d4af..097522e7994 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -1,75 +1,75 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch index cedd1dcfc01..f47a394bbf0 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT.launch @@ -1,73 +1,73 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java index 850f49d680b..0eb19bac8e6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java @@ -144,6 +144,9 @@ public class RegattaActivity extends AbstractActivity { List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL)); List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_DETAIL)); List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL)); + if (overallDetails.isEmpty()) { + overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); + } List legDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_LEG_DETAIL)); List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME)); boolean showAddedScores = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) ? diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java index d781e2cfa12..eeb3714d515 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java @@ -145,6 +145,9 @@ public class SeriesActivity extends AbstractActivity { List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL)); List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_DETAIL)); List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL)); + if (overallDetails.isEmpty()) { + overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); + } List legDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_LEG_DETAIL)); List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME)); boolean showAddedScores = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) ? diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 710e03455c4..35019d4437b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -219,6 +219,9 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { List maneuverDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_MANEUVER_DETAIL)); List raceDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_DETAIL)); List overallDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_OVERALL_DETAIL)); + if (overallDetails.isEmpty()) { + overallDetails = Collections.singletonList(DetailType.REGATTA_RANK); + } List legDetails = getDetailTypeListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_LEG_DETAIL)); List namesOfRacesToShow = getStringListFromParamValue(parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME)); boolean showAddedScores = parameterMap.containsKey(LeaderboardUrlSettings.PARAM_SHOW_ADDED_SCORES) ? diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index b2bac47247d..85d9e93c173 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -227,9 +227,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private boolean showAddedScores; private boolean showCompetitorSailId; - private int competitorSailIdColumnIndex = -1; private boolean showCompetitorFullName; - private int competitorFullNameColumnIndex = -1; /** * When true then an additional column just before the overall points is displayed that sums up @@ -2498,7 +2496,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS int columnIndex = 0; columnIndex = ensureSelectionCheckboxColumn(columnIndex); columnIndex = ensureRankColumn(columnIndex); - columnIndex = ensureSailIDAndCompetitorColumn(columnIndex); + columnIndex = ensureSailIDAndCompetitorColumn(); columnIndex = updateCarryColumn(leaderboard, columnIndex); adjustOverallDetailColumns(leaderboard, columnIndex); // first remove race columns no longer needed: @@ -2820,50 +2818,42 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS * method will always make sure that two columns are placed in the layout, one at * sailIdColumnIndex and one at sailIdColumnIndex+1. */ - private int ensureSailIDAndCompetitorColumn(int sailIdColumnIndex) { - final int nextColumnIndex; - int columnCounter = 0; - int leaderboardColumnCount = getLeaderboardTable().getColumnCount(); - if (isShowCompetitorSailId()) { - if (competitorSailIdColumnIndex == -1) { - competitorSailIdColumnIndex = sailIdColumnIndex; - if (leaderboardColumnCount > 1) { - insertColumn(competitorSailIdColumnIndex, new SailIDColumn(new CompetitorFetcher() { - @Override - public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { - return t.competitor; - } - })); - } else { - addColumn(new SailIDColumn(new CompetitorFetcher() { - @Override - public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { - return t.competitor; - } - })); + private int ensureSailIDAndCompetitorColumn() { + SailIDColumn sailIdColumn = new SailIDColumn(new CompetitorFetcher() { + @Override + public CompetitorDTO getCompetitor(LeaderboardRowDTO t) { + return t.competitor; + } + }); + if (getLeaderboardTable().getColumnCount() >= 3) { // table already filled with columns + if (isShowCompetitorSailId()) { + if (!(getLeaderboardTable().getColumn(2) instanceof SailIDColumn)) { + insertColumn(2, sailIdColumn); + } + } else { + if (getLeaderboardTable().getColumn(2) instanceof SailIDColumn) { + removeColumn(2); } } - columnCounter++; - } else if (!isShowCompetitorSailId() && competitorSailIdColumnIndex != -1) { - removeColumn(competitorSailIdColumnIndex); - competitorSailIdColumnIndex = -1; - } - if (isShowCompetitorFullName()) { - if (competitorFullNameColumnIndex == -1) { - if (leaderboardColumnCount > 1) { - insertColumn(sailIdColumnIndex+columnCounter, createCompetitorColumn()); - } else { - addColumn(createCompetitorColumn()); + final int competitorFullNameColumnIndex = 2 + (isShowCompetitorSailId() ? 1 : 0); + if (isShowCompetitorFullName()) { + if (!(getLeaderboardTable().getColumn(competitorFullNameColumnIndex) instanceof CompetitorColumn)) { + insertColumn(competitorFullNameColumnIndex, createCompetitorColumn()); + } + } else { + if (getLeaderboardTable().getColumn(competitorFullNameColumnIndex) instanceof CompetitorColumn) { + removeColumn(competitorFullNameColumnIndex); } - competitorFullNameColumnIndex = sailIdColumnIndex+columnCounter; } - columnCounter++; - } else if (!isShowCompetitorFullName() && competitorFullNameColumnIndex != -1) { - removeColumn(competitorFullNameColumnIndex); - competitorFullNameColumnIndex = -1; + } else { // table just being initialized + if (isShowCompetitorSailId()) { + addColumn(sailIdColumn); + } + if (isShowCompetitorFullName()) { + addColumn(createCompetitorColumn()); + } } - nextColumnIndex = sailIdColumnIndex + columnCounter; - return nextColumnIndex; + return (isShowRegattaRankColumn() ? 1 : 0) + (isShowCompetitorSailId() ? 1 : 0) + (isShowCompetitorFullName() ? 1 : 0); } protected CompetitorColumn createCompetitorColumn() { diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 4d4cb9afe18..bc1f6674e4a 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch index dede2cd8d60..7fd34ef3dd9 100755 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -1,47 +1,47 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d021fc3750af59b2cb47450e43ee825f1b5db036 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Tue, 3 Mar 2015 13:31:36 +0100 Subject: [PATCH 244/264] Fixed glitch with additional columns being misplaced in leaderboard --- .../com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index 85d9e93c173..f9711712d90 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -2853,7 +2853,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS addColumn(createCompetitorColumn()); } } - return (isShowRegattaRankColumn() ? 1 : 0) + (isShowCompetitorSailId() ? 1 : 0) + (isShowCompetitorFullName() ? 1 : 0); + return (isShowRegattaRankColumn() ? 2 : 1) + (isShowCompetitorSailId() ? 1 : 0) + (isShowCompetitorFullName() ? 1 : 0); } protected CompetitorColumn createCompetitorColumn() { From 7c995c500f6c791429ee99f97e8ce3ec67f8c299 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Tue, 3 Mar 2015 13:33:22 +0100 Subject: [PATCH 245/264] Adjusted documentation --- .../sailing/gwt/ui/leaderboard/LeaderboardPanel.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index f9711712d90..2e572b80fa9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -2807,16 +2807,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } /** - * Assumes that the sail ID and competitor column are mandatory elements of a leaderboard; hence, if the leaderboard - * has columns where sail ID and competitor names are expected, it is silently assumed that those are already in the - * correct place because these columns are never optional. - * - * @param sailIdColumnIndex - * the 0-based index where the sail ID column is to be placed; the competitor name column will be at the - * index greater by one - * @return the 0-based index for the next column; two greater than sailIdColumnIndex because this - * method will always make sure that two columns are placed in the layout, one at - * sailIdColumnIndex and one at sailIdColumnIndex+1. + * @return the 0-based index for the next column */ private int ensureSailIDAndCompetitorColumn() { SailIDColumn sailIdColumn = new SailIDColumn(new CompetitorFetcher() { From b3937596b9b2049f06f15f625c8c64dc23239b5a Mon Sep 17 00:00:00 2001 From: Samuel Oey Date: Tue, 3 Mar 2015 14:23:04 +0000 Subject: [PATCH 246/264] Updated api v1 (markdown) --- wiki/tracking-app/api-v1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wiki/tracking-app/api-v1.md b/wiki/tracking-app/api-v1.md index 78db7439ae0..0d634ea53d0 100644 --- a/wiki/tracking-app/api-v1.md +++ b/wiki/tracking-app/api-v1.md @@ -53,8 +53,8 @@ This information is represented in a URL with the following structure: ``` http:///tracking/checkin ?event_id= - &leaderboard_name= - &competitor_id= + &leaderboardName= + &competitorId= ``` **Additional Notes:** From d70c11c0dd20792f29b9984f2172ea43f7b5a68b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 15:24:25 +0100 Subject: [PATCH 247/264] applied selection checkbox also for other competitor tables --- .../gwt/ui/adminconsole/CompetitorPanel.java | 6 ++- .../adminconsole/CompetitorTableWrapper.java | 39 ++++++++++++++++++- .../ImportFixesAndAddMappingsDialog.java | 6 ++- .../ItemToMapToDeviceSelectionPanel.java | 6 ++- ...TrackingCompetitorRegistrationsDialog.java | 9 +++-- .../gwt/ui/adminconsole/TableWrapper.java | 2 +- 6 files changed, 55 insertions(+), 13 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java index 54f8369f8ad..51ad785a15d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java @@ -61,8 +61,10 @@ public class CompetitorPanel extends SimplePanel { this.stringMessages = stringMessages; this.errorReporter = errorReporter; this.leaderboardName = leaderboardName; - this.competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, - new MultiSelectionModel(), false); + final Class selectionModelType = MultiSelectionModel.class; + @SuppressWarnings("unchecked") + final Class> multiSelectionType = (Class>) selectionModelType; + this.competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, multiSelectionType, false); this.competitorSelectionModel = competitorTable.getSelectionModel(); VerticalPanel mainPanel = new VerticalPanel(); this.setWidget(mainPanel); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 2f1acd06de2..29ad144265e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -6,6 +6,7 @@ import java.util.List; import com.google.gwt.cell.client.SafeHtmlCell; import com.google.gwt.core.client.Callback; +import com.google.gwt.core.client.GWT; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; @@ -15,23 +16,32 @@ import com.google.gwt.user.cellview.client.TextColumn; 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.ListDataProvider; import com.google.gwt.view.client.SelectionModel; +import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.impl.NaturalComparator; +import com.sap.sailing.gwt.ui.adminconsole.AdminConsoleTableResources.AdminConsoleTableStyle; 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; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sse.common.Color; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; public class CompetitorTableWrapper> extends TableWrapper { + private static final AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); + private static final AdminConsoleTableStyle tableSTyle = tableResources.cellTableStyle(); + private final LabeledAbstractFilterablePanel filterField; + + private final S selectionModel; public CompetitorTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,ErrorReporter errorReporter, - S selectionModel, boolean enablePager) { - super(sailingService, stringMessages, errorReporter, selectionModel, enablePager); + Class selectionModelType, boolean enablePager) { + super(sailingService, stringMessages, errorReporter, /* selectionModel will be set later below */ null, enablePager); ListHandler competitorColumnListHandler = new ListHandler(dataProvider.getList()); @@ -131,6 +141,26 @@ public class CompetitorTableWrapper> ext }; mainPanel.insert(filterField, 0); + if (selectionModelType.getName().contains("Multi")) { + // add a selection checkbox column + SelectionCheckboxColumn selectionCheckboxColumn = new SelectionCheckboxColumn( + tableSTyle.cellTableCheckboxSelected(), tableSTyle.cellTableCheckboxDeselected(), tableSTyle.cellTableCheckboxColumnCell()) { + @Override + protected ListDataProvider getListDataProvider() { + return dataProvider; + } + }; + @SuppressWarnings("unchecked") + S mySelectionModel = (S) selectionCheckboxColumn.getSelectionModel(); + selectionModel = mySelectionModel; + table.setSelectionModel(selectionModel, selectionCheckboxColumn.getSelectionManager()); + table.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader()); + } else { + @SuppressWarnings("unchecked") + S mySelectionModel = (S) new SingleSelectionModel(); + selectionModel = mySelectionModel; + table.setSelectionModel(selectionModel); + } table.addColumnSortHandler(competitorColumnListHandler); table.addColumn(sailIdColumn, stringMessages.sailNumber()); table.addColumn(competitorNameColumn, stringMessages.name()); @@ -139,6 +169,11 @@ public class CompetitorTableWrapper> ext table.addColumn(competitorIdColumn, stringMessages.id()); } + @Override + public S getSelectionModel() { + return selectionModel; + } + public Iterable getAllCompetitors() { return filterField.getAll(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java index b86541d42a9..03db328db33 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImportFixesAndAddMappingsDialog.java @@ -60,8 +60,10 @@ public class ImportFixesAndAddMappingsDialog extends DataEntryDialog(sailingService, stringMessages, errorReporter, - new SingleSelectionModel(), true); + final Class selectionModelType = SingleSelectionModel.class; + @SuppressWarnings("unchecked") + final Class> singleSelectionType = (Class>) selectionModelType; + competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, singleSelectionType, true); markTable = new MarkTableWrapper>( new SingleSelectionModel(), sailingService, stringMessages, errorReporter); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java index 2941c6b4758..61dd9736f6c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java @@ -35,9 +35,11 @@ public class ItemToMapToDeviceSelectionPanel implements IsWidget { this.selected = selected; this.errorReporter = errorReporter; + final Class selectionModelType = SingleSelectionModel.class; + @SuppressWarnings("unchecked") + final Class> singleSelectionType = (Class>) selectionModelType; + competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, singleSelectionType, true); final SingleSelectionModel markSelectionModel = new SingleSelectionModel(); - competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, - new SingleSelectionModel(), true); markTable = new MarkTableWrapper>(markSelectionModel, sailingService, stringMessages, errorReporter); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java index 80ddf34d978..7249edae979 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingCompetitorRegistrationsDialog.java @@ -88,10 +88,11 @@ public class RaceLogTrackingCompetitorRegistrationsDialog extends AbstractSaveDi mainPanel.add(panel); CaptionPanel allCompetitorsPanel = new CaptionPanel(stringMessages.competitorPool()); CaptionPanel registeredCompetitorsPanel = new CaptionPanel(stringMessages.registeredCompetitors()); - allCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, - new MultiSelectionModel(), true); - registeredCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, - new MultiSelectionModel(), true); + final Class selectionModelType = MultiSelectionModel.class; + @SuppressWarnings("unchecked") + final Class> multiSelectionType = (Class>) selectionModelType; + allCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, multiSelectionType, true); + registeredCompetitorsTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, multiSelectionType, true); allCompetitorsPanel.add(allCompetitorsTable); registeredCompetitorsPanel.add(registeredCompetitorsTable); VerticalPanel movePanel = new VerticalPanel(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java index 9be6496a3a4..9cac31faafd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java @@ -19,7 +19,7 @@ import com.sap.sse.gwt.client.ErrorReporter; */ public abstract class TableWrapper> implements IsWidget { protected final CellTable table; - protected final S selectionModel; + private final S selectionModel; protected final ListDataProvider dataProvider; protected VerticalPanel mainPanel; protected final SailingServiceAsync sailingService; From c83628dae1ed8f0dff1a148ed655cf2d38f116ba Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 16:11:41 +0100 Subject: [PATCH 248/264] i18n for no event selected --- .../gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java index 27602739cd7..a23c10ea230 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddDeviceMappingToRegattaLogDialog.java @@ -95,7 +95,7 @@ public class AddDeviceMappingToRegattaLogDialog extends AbstractCancelableDialog public String createURL(String baseUrlWithoutTrailingSlash, String mappedItemType, String mappedItemId) throws QRCodeURLCreationException { if (events.getValue() == null) { - throw new QRCodeURLCreationException("no event selected"); + throw new QRCodeURLCreationException(stringMessages.noEventSelected()); } String eventIdAsString = events.getValue().id.toString(); return DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(baseUrlWithoutTrailingSlash, eventIdAsString, From 4e9fc0d8ceec3ded15a8d1a95585a5d199355840 Mon Sep 17 00:00:00 2001 From: Simon Pamies Date: Tue, 3 Mar 2015 16:13:09 +0100 Subject: [PATCH 249/264] Making GWT server always selecting a random port to make sure that there are no clashes --- .../SailingDashboardsGWT sdm.launch | 3 ++- java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch | 3 ++- java/com.sap.sailing.gwt.ui/SailingGWT.launch | 3 ++- java/com.sap.sse.security.ui/Security UI sdm.launch | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch index 931c405bff9..694e0ceb278 100644 --- a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch +++ b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch @@ -4,6 +4,7 @@ + @@ -34,7 +35,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index 097522e7994..0423a6e759c 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -3,6 +3,7 @@ + @@ -69,7 +70,7 @@ - + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch index f47a394bbf0..f02353541f2 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT.launch @@ -3,6 +3,7 @@ + @@ -67,7 +68,7 @@ - + diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch index 7fd34ef3dd9..52862c5ed50 100755 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -4,6 +4,7 @@ + @@ -41,7 +42,7 @@ - + From 28db671b1a766d7d6e46c5a484867aaab969565a Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 16:32:39 +0100 Subject: [PATCH 250/264] addressing bug 2663 comment #9: the SafeHtmlBuilder.appendHtmlConstant does only accept full elements --- .../ui/adminconsole/CompetitorTableWrapper.java | 7 ++----- .../sap/sailing/gwt/ui/client/StringMessages.java | 3 ++- .../gwt/ui/client/StringMessages.properties | 1 + .../gwt/ui/client/StringMessages_de.properties | 3 ++- .../gwt/ui/client/StringMessages_ru.properties | 3 ++- .../sap/sse/gwt/adminconsole/StringMessages.java | 2 ++ .../gwt/adminconsole/URLFieldWithFileUpload.java | 15 ++++++++++----- 7 files changed, 21 insertions(+), 13 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 6ad83b0e296..5fec16eca5f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -106,11 +106,8 @@ public class CompetitorTableWrapper> ext public SafeHtml getValue(CompetitorDTO competitor) { SafeHtmlBuilder sb = new SafeHtmlBuilder(); if (competitor.getImageURL() != null && !competitor.getImageURL().isEmpty()) { - sb.appendHtmlConstant(""); + sb.appendHtmlConstant(""); } return sb.toSafeHtml(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 580a8a2b84d..18c85e8ad72 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -1152,6 +1152,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String selectEventForInvitation(); String imageURL(); String upload(); - String removeResult(JSONValue jsonValue, JSONValue jsonValue2); + String removeResult(JSONValue status, JSONValue message); String uploadSuccessful(); + String fileUploadResult(JSONValue status, JSONValue message); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index ccadff6adcf..c23a1f98bf3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1134,3 +1134,4 @@ imageURL=Image URL upload=Upload removeResult=Remove operation: {0} {1} uploadSuccessful=Upload successful +fileUploadResult=File upload: {0} {1} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 941eeed7472..76c77c038e8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1123,4 +1123,5 @@ selectEventForInvitation=Event für Einladung auswählen imageURL=Bild-URL upload=Hochladen removeResult=Löschergebnis: {0} {1} -uploadSuccessful=Hochladen erfolgreich \ No newline at end of file +uploadSuccessful=Hochladen erfolgreich +fileUploadResult=Ergebnis des Hochladens: {0} {1} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 60e4997b279..a499b19063f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1072,4 +1072,5 @@ selectEventForInvitation=Select Event for Invitation imageURL=Image URL upload=Upload removeResult=Remove operation: {0} {1} -uploadSuccessful=Upload successful \ No newline at end of file +uploadSuccessful=Upload successful +fileUploadResult=File upload: {0} {1} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java index 71126c2c231..27138ce5644 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/StringMessages.java @@ -10,4 +10,6 @@ public interface StringMessages extends Messages { String removeResult(String status, String message); String uploadSuccessful(); + + String fileUploadResult(String status, String message); } diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java index 9db1a36cd76..579a9424548 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -78,11 +78,16 @@ public class URLFieldWithFileUpload extends HorizontalPanel { public void onSubmitComplete(SubmitCompleteEvent event) { String result = event.getResults(); JSONArray resultJson = (JSONArray) JSONParser.parseLenient(result.replaceFirst("]*>(.*)", "$1")); - if (resultJson != null && resultJson.get(0).isObject().get("file_uri") != null) { - uri = resultJson.get(0).isObject().get("file_uri").isString().stringValue(); - urlTextBox.setValue(uri); - removeButton.setEnabled(true); - Window.alert(stringMessages.uploadSuccessful()); + if (resultJson != null) { + if (resultJson.get(0).isObject().get("file_uri") != null) { + uri = resultJson.get(0).isObject().get("file_uri").isString().stringValue(); + urlTextBox.setValue(uri); + removeButton.setEnabled(true); + Window.alert(stringMessages.uploadSuccessful()); + } else { + Window.alert(stringMessages.fileUploadResult(resultJson.get(0).isObject().get("status").isString().stringValue(), + resultJson.get(0).isObject().get("message").isString().stringValue())); + } } } }); From ccf217b015f3e7ac716f83826e051ee40493c3fe Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 17:42:30 +0100 Subject: [PATCH 251/264] properly throw OperationFailedException when something goes wrong with S3 file upload --- .../impl/AmazonS3FileStorageServiceImpl.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java index f4758b229c1..56b6a7bd218 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java @@ -101,16 +101,12 @@ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl i final PutObjectRequest request = new PutObjectRequest(bucketName.getValue(), key, is, metadata) .withCannedAcl(CannedAccessControlList.PublicRead); final AmazonS3Client s3Client = createS3Client(); - - new Thread() { - public void run() { - try { - s3Client.putObject(request); - } catch (AmazonClientException e) { - logger.log(Level.WARNING, "Could not store file", e); - } - }; - }.run(); + try { + s3Client.putObject(request); + } catch (AmazonClientException e) { + logger.log(Level.SEVERE, "Could not store file", e); + throw new OperationFailedException(e.getMessage(), e); + } URI uri = getUri(key); logger.info("Stored file " + uri); return uri; From 4f593b08d3165d8f18235f321bad02de4c844c8f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 17:44:03 +0100 Subject: [PATCH 252/264] improved error message in case of file upload --- .../sap/sailing/server/gateway/impl/FileUploadServlet.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java index 0a2e67926c5..15d05cc0f1d 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java @@ -17,6 +17,7 @@ import org.apache.commons.fileupload.FileItem; import org.json.simple.JSONArray; import org.json.simple.JSONObject; +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.filestorage.InvalidPropertiesException; import com.sap.sse.filestorage.OperationFailedException; @@ -66,8 +67,8 @@ public class FileUploadServlet extends AbstractFileUploadServlet { .storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize()); result.put(JSON_FILE_NAME, fileItem.getName()); result.put(JSON_FILE_URI, fileUri.toString()); - } catch (IOException | OperationFailedException | InvalidPropertiesException e) { - final String errorMessage = "Could not store file: " + e.getMessage(); + } catch (IOException | OperationFailedException | InvalidPropertiesException | NoCorrespondingServiceRegisteredException e) { + final String errorMessage = "Could not store file"+ (e.getMessage()==null?"":(": " + e.getMessage())); logger.log(Level.WARNING, "Could not store file", e); result.put("status", Status.INTERNAL_SERVER_ERROR.name()); result.put("message", errorMessage); From fe3ce9f1079c15bf5cea7367317106ff799dc441 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 17:46:24 +0100 Subject: [PATCH 253/264] improved file storage error messages --- .../com/sap/sailing/gwt/ui/client/StringMessages.properties | 2 +- .../com/sap/sailing/gwt/ui/client/StringMessages_de.properties | 2 +- .../com/sap/sailing/gwt/ui/client/StringMessages_ru.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 9469dc63fc8..38655ee6531 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -1134,6 +1134,6 @@ imageURL=Image URL upload=Upload removeResult=Remove operation: {0} {1} uploadSuccessful=Upload successful -fileUploadResult=File upload: {0} {1} +fileUploadResult=File upload: {0} {1}.\nIn case of error please check that your File Storage service is properly configured. showCompetitorSailIdColumn=Show Competitor Sail Id showCompetitorFullNameColumn=Show Competitor Full Name diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 12a000a7e1e..3890017f3c1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -1124,6 +1124,6 @@ imageURL=Bild-URL upload=Hochladen removeResult=Löschergebnis: {0} {1} uploadSuccessful=Hochladen erfolgreich -fileUploadResult=Ergebnis des Hochladens: {0} {1} +fileUploadResult=Ergebnis des Hochladens: {0} {1}\nIm Fehlerfall bitte die Konfiguration für "Datei Speicherung" prüfen. showCompetitorSailIdColumn=Sail Id anzeigen showCompetitorFullNameColumn=Vollen Namen anzeigen diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 0030f6c7f2d..8302679bb69 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1073,6 +1073,6 @@ imageURL=Image URL upload=Upload removeResult=Remove operation: {0} {1} uploadSuccessful=Upload successful -fileUploadResult=File upload: {0} {1} +fileUploadResult=File upload: {0} {1}.\nIn case of error please check that your File Storage service is properly configured. showCompetitorSailIdColumn=Show Competitor Sail Id showCompetitorFullNameColumn=Show Competitor Full Name From c48407634d5c1840596f2f188e6ab8d507ad3ce3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 17:46:55 +0100 Subject: [PATCH 254/264] added failing test case for bug 2676, S3 uploader producing incorrect URLs --- .../sse/filestorage/services/test/AmazonS3Test.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/services/test/AmazonS3Test.java b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/services/test/AmazonS3Test.java index 5d96698b4c9..0550a90b196 100644 --- a/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/services/test/AmazonS3Test.java +++ b/java/com.sap.sse.filestorage.test/src/com/sap/sse/filestorage/services/test/AmazonS3Test.java @@ -1,5 +1,7 @@ package com.sap.sse.filestorage.services.test; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -36,8 +38,10 @@ public class AmazonS3Test { InputStream downloadStream = uri.toURL().openStream(); stream = getClass().getClassLoader().getResourceAsStream(teamImageFile); - IOUtils.contentEquals(downloadStream, stream); - - storageService.removeFile(uri); + try { + assertTrue(IOUtils.contentEquals(downloadStream, stream)); + } finally { + storageService.removeFile(uri); + } } } From 726a30d82e11d2beb011acad27b7926f0ceda91b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 18:03:18 +0100 Subject: [PATCH 255/264] fixed failing test for S3 uploader, working towards bug 2676 --- .../FileStorageStringMessages_de.properties | 1 + .../FileStorageStringMessages_en.properties | 1 + .../impl/AmazonS3FileStorageServiceImpl.java | 12 +++++------- .../filestorage/testsupport/AmazonS3TestSupport.java | 2 ++ 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_de.properties b/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_de.properties index a323dfb22bc..add4e29981e 100644 --- a/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_de.properties +++ b/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_de.properties @@ -2,6 +2,7 @@ s3Desc=Dateien in Amazon S3 speichern. Dateiname wird als aus zuf s3AccessIdDesc=Zugriffs-ID (leer lassen, um ~/.aws/credentials zu benutzen) s3AccessKeyDesc=Geheimer Zugrifss-Schlüssel (leer lassen, um ~/.aws/credentials zu benutzen) s3BucketNameDesc=Name des "Buckets" der benutzt werden soll (muss in S3 existieren, und die Zugriffsrechte müssen korrekt gesetzt sein). +s3EndpointDesc=Die "Endpoint"-Adresse, so, wie sie in den Eigenschaften des S3 Buckets angegeben ist; beispielsweise .s3-website-eu-west-1.amazonaws.com, abhängig von der Region. localDesc=Dateien werden im lokalen Dateisystem gespeichert. Sie werden in localPath+fileName gespeichert, der Zugriff erfolgt über baseUrl+fileName. Bitte beachten: baseUrl muss auch für andere Server-Instanzen erreichbar sein. Das kann z.B. durch ein Mounten eines auf static.sapsailing.com gehosteten Netwerk-Dateisystems unter localPath erreicht werden. localBaseUrlDesc=Base URL + fileName = URL unter der die hochgeladene Datei erreichbar ist localLocalPathDesc=Lokaler Pfad zum Ablegen der Dateien \ No newline at end of file diff --git a/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties b/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties index 558835e6078..779a563d980 100644 --- a/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties +++ b/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties @@ -2,6 +2,7 @@ s3Desc=Store files on Amazon S3. The resulting file name is a random UUID plus t s3AccessIdDesc=Access ID (leave blank to use ~/.aws/credentials instead) s3AccessKeyDesc=Secret Access Key (leave blank to use ~/.aws/credentials instead) s3BucketNameDesc=Name of Bucket to use (has to already exist, and user needs sufficient permissions) +s3EndpointDesc=The "Endpoint" as listed in the bucket properties; usually something like .s3-website-eu-west-1.amazonaws.com, depending on region. localDesc=Service for storing files in the local file system. Files get stored in localPath+fileName and can be accessed at baseUrl+fileName Note that the content lying in baseUrl must therefore be accessible remotely. This can for examplebe achieved by mounting a remote file system for example from static.sapsailing.com on the replicas to localPath. localBaseUrlDesc=Base URL + fileName = URL where uploaded file will be accessible localLocalPathDesc=Local Path to use for file storage \ No newline at end of file diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java index 56b6a7bd218..46ced37f0c9 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java @@ -27,9 +27,8 @@ import com.sap.sse.filestorage.OperationFailedException; * following file: ~/.aws/credentials and add credentials to it (get the access id and secret key from * https://console.aws.amazon.com/iam/home?#security_credential). * - * TODO configure credentials in AdminConsole TODO configure bucket name in AdminConsole - * * @author Fredrik Teschke + * @author Axel Uhl * */ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl implements FileStorageService { @@ -38,9 +37,7 @@ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl i private static final Logger logger = Logger.getLogger(AmazonS3FileStorageServiceImpl.class.getName()); - private static final String baseUrl = "s3.amazonaws.com"; private static final String retrievalProtocol = "http"; - // private static final String bucketName = "ftes-sap-sailing"; private final FileStorageServicePropertyImpl accessId = new FileStorageServicePropertyImpl("accessId", false, "s3AccessIdDesc"); @@ -48,10 +45,12 @@ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl i "s3AccessKeyDesc"); private final FileStorageServicePropertyImpl bucketName = new FileStorageServicePropertyImpl("bucketName", true, "s3BucketNameDesc"); + private final FileStorageServicePropertyImpl endpoint = new FileStorageServicePropertyImpl("endpoint", true, + "s3EndpointDesc"); public AmazonS3FileStorageServiceImpl() { super(NAME, "s3Desc"); - addProperties(accessId, accessKey, bucketName); + addProperties(accessId, accessKey, bucketName, endpoint); } private AmazonS3Client createS3Client() throws InvalidPropertiesException { @@ -60,7 +59,6 @@ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl i // first try to use properties if (accessId.getValue() != null && accessKey.getValue() != null) { creds = new BasicAWSCredentials(accessId.getValue(), accessKey.getValue()); - } else { // if properties are empty, read credentials from ~/.aws/credentials try { @@ -84,7 +82,7 @@ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl i try { // FIXME: region is missing s3-... see: // http://stackoverflow.com/questions/10975475/amazon-s3-upload-file-and-get-url - return new URI(retrievalProtocol, baseUrl, "/" + bucketName.getValue() + "/" + key, null); + return new URI(retrievalProtocol, endpoint.getValue(), "/" + key, null); } catch (URISyntaxException e) { logger.log(Level.WARNING, "Could not create URI for uploaded file with key " + key, e); return null; diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java index 6715d79cc62..0f839db3fef 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java @@ -7,12 +7,14 @@ public class AmazonS3TestSupport { public static final String s3AccessId = "AKIAJOX7PZ6ACI2FQU4A"; public static final String s3AccessKey = "NkijH2DfhWgb9fmESPjpeIbpUF+tC220KyTOfvGJ"; private static final String s3BucketName = "sapsailing-automatic-upload-test"; + private static final String s3BucketEndpoint = "sapsailing-automatic-upload-test.s3-website-eu-west-1.amazonaws.com"; public static AmazonS3FileStorageServiceImpl createService() throws InvalidPropertiesException { AmazonS3FileStorageServiceImpl service = new AmazonS3FileStorageServiceImpl(); service.internalSetProperty("accessId", s3AccessId); service.internalSetProperty("accessKey", s3AccessKey); service.internalSetProperty("bucketName", s3BucketName); + service.internalSetProperty("endpoint", s3BucketEndpoint); service.testProperties(); return service; } From 475564698f1aa4c2a32e50f4e37631b9e4cae351 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 18:25:04 +0100 Subject: [PATCH 256/264] added region hostname for S3 retrieval, solving bug 2676 --- .../sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java index 46ced37f0c9..652f4d58322 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java @@ -38,6 +38,7 @@ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl i private static final Logger logger = Logger.getLogger(AmazonS3FileStorageServiceImpl.class.getName()); private static final String retrievalProtocol = "http"; + private static final String regionRetrievalHost = "s3-eu-west-1.amazonaws.com"; private final FileStorageServicePropertyImpl accessId = new FileStorageServicePropertyImpl("accessId", false, "s3AccessIdDesc"); @@ -80,9 +81,7 @@ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl i private URI getUri(String key) { try { - // FIXME: region is missing s3-... see: - // http://stackoverflow.com/questions/10975475/amazon-s3-upload-file-and-get-url - return new URI(retrievalProtocol, endpoint.getValue(), "/" + key, null); + return new URI(retrievalProtocol, regionRetrievalHost, "/" + bucketName.getValue() + "/" + key, null); } catch (URISyntaxException e) { logger.log(Level.WARNING, "Could not create URI for uploaded file with key " + key, e); return null; From b2401626ffcff47a562e26a87cbdf76cd0945470 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 18:27:44 +0100 Subject: [PATCH 257/264] removed the now extra endpoint property again from S3 storage service --- .../stringmessages/FileStorageStringMessages_de.properties | 1 - .../stringmessages/FileStorageStringMessages_en.properties | 1 - .../sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java | 4 +--- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_de.properties b/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_de.properties index add4e29981e..a323dfb22bc 100644 --- a/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_de.properties +++ b/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_de.properties @@ -2,7 +2,6 @@ s3Desc=Dateien in Amazon S3 speichern. Dateiname wird als aus zuf s3AccessIdDesc=Zugriffs-ID (leer lassen, um ~/.aws/credentials zu benutzen) s3AccessKeyDesc=Geheimer Zugrifss-Schlüssel (leer lassen, um ~/.aws/credentials zu benutzen) s3BucketNameDesc=Name des "Buckets" der benutzt werden soll (muss in S3 existieren, und die Zugriffsrechte müssen korrekt gesetzt sein). -s3EndpointDesc=Die "Endpoint"-Adresse, so, wie sie in den Eigenschaften des S3 Buckets angegeben ist; beispielsweise .s3-website-eu-west-1.amazonaws.com, abhängig von der Region. localDesc=Dateien werden im lokalen Dateisystem gespeichert. Sie werden in localPath+fileName gespeichert, der Zugriff erfolgt über baseUrl+fileName. Bitte beachten: baseUrl muss auch für andere Server-Instanzen erreichbar sein. Das kann z.B. durch ein Mounten eines auf static.sapsailing.com gehosteten Netwerk-Dateisystems unter localPath erreicht werden. localBaseUrlDesc=Base URL + fileName = URL unter der die hochgeladene Datei erreichbar ist localLocalPathDesc=Lokaler Pfad zum Ablegen der Dateien \ No newline at end of file diff --git a/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties b/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties index 779a563d980..558835e6078 100644 --- a/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties +++ b/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties @@ -2,7 +2,6 @@ s3Desc=Store files on Amazon S3. The resulting file name is a random UUID plus t s3AccessIdDesc=Access ID (leave blank to use ~/.aws/credentials instead) s3AccessKeyDesc=Secret Access Key (leave blank to use ~/.aws/credentials instead) s3BucketNameDesc=Name of Bucket to use (has to already exist, and user needs sufficient permissions) -s3EndpointDesc=The "Endpoint" as listed in the bucket properties; usually something like .s3-website-eu-west-1.amazonaws.com, depending on region. localDesc=Service for storing files in the local file system. Files get stored in localPath+fileName and can be accessed at baseUrl+fileName Note that the content lying in baseUrl must therefore be accessible remotely. This can for examplebe achieved by mounting a remote file system for example from static.sapsailing.com on the replicas to localPath. localBaseUrlDesc=Base URL + fileName = URL where uploaded file will be accessible localLocalPathDesc=Local Path to use for file storage \ No newline at end of file diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java index 652f4d58322..ff6fba44c4b 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/impl/AmazonS3FileStorageServiceImpl.java @@ -46,12 +46,10 @@ public class AmazonS3FileStorageServiceImpl extends BaseFileStorageServiceImpl i "s3AccessKeyDesc"); private final FileStorageServicePropertyImpl bucketName = new FileStorageServicePropertyImpl("bucketName", true, "s3BucketNameDesc"); - private final FileStorageServicePropertyImpl endpoint = new FileStorageServicePropertyImpl("endpoint", true, - "s3EndpointDesc"); public AmazonS3FileStorageServiceImpl() { super(NAME, "s3Desc"); - addProperties(accessId, accessKey, bucketName, endpoint); + addProperties(accessId, accessKey, bucketName); } private AmazonS3Client createS3Client() throws InvalidPropertiesException { From 641740cc215e64c6ec98b841b8f3b6850c754cf3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 18:32:01 +0100 Subject: [PATCH 258/264] added e-mail column to competitor table --- .../ui/adminconsole/CompetitorTableWrapper.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 5fec16eca5f..c63455baa4d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -127,7 +127,6 @@ public class CompetitorTableWrapper> ext return competitor.getIdAsString(); } }; - competitorIdColumn.setSortable(true); competitorColumnListHandler.setComparator(competitorIdColumn, new Comparator() { @Override @@ -136,6 +135,20 @@ public class CompetitorTableWrapper> ext } }); + TextColumn competitorEMailColumn = new TextColumn() { + @Override + public String getValue(CompetitorDTO competitor) { + return competitor.getEmail(); + } + }; + competitorEMailColumn.setSortable(true); + competitorColumnListHandler.setComparator(competitorEMailColumn, new Comparator() { + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return new NaturalComparator(false).compare(o1.getEmail(), o2.getEmail()); + } + }); + filterField = new LabeledAbstractFilterablePanel(new Label(stringMessages.filterCompetitors()), new ArrayList(), table, dataProvider) { @Override @@ -155,6 +168,7 @@ public class CompetitorTableWrapper> ext table.addColumn(boatClassColumn, stringMessages.boatClass()); table.addColumn(displayColorColumn, stringMessages.color()); table.addColumn(imageColumn, stringMessages.imageURL()); + table.addColumn(competitorEMailColumn, stringMessages.email()); table.addColumn(competitorIdColumn, stringMessages.id()); } From ae8cbe2f58f6ff3265ffa3d767a393cda1e13666 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 22:54:20 +0100 Subject: [PATCH 259/264] fixed some typos --- .../src/com/sap/sailing/polars/PolarDataService.java | 2 +- .../src/com/sap/sailing/polars/mining/PolarDataMiner.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java index 63209c8a42e..96e04676306 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java @@ -35,7 +35,7 @@ import com.sap.sse.common.Util.Pair; * which also returns the average angle for the parameters provided. * * @author Frederik Petersen (D054528) - * @autho Axel Uhl (D043530) + * @author Axel Uhl (D043530) * */ public interface PolarDataService { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java index 24da4cc02e6..188b207983f 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java @@ -197,7 +197,7 @@ public class PolarDataMiner { // The following is an estimation function. It only serves as a fallback. It's the same for all boatclasses and returns // default maneuver angles. - // The function is able to return boat speed values for windspeed alues between 5kn and 25kn , which are some kind of realistic + // The function is able to return boat speed values for windspeed values between 5kn and 25kn , which are some kind of realistic // for sailing boats. They are taken from the 505 polars we gathered in the races until now. Set> resultSet = new HashSet<>(); From b3c3cabbd7f4924a9dc2b43741d3459576c10f5b Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 3 Mar 2015 23:04:20 +0100 Subject: [PATCH 260/264] removed additional endpoint S3 property again also from test support --- .../com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java index 0f839db3fef..4acad0939ac 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java @@ -14,7 +14,6 @@ public class AmazonS3TestSupport { service.internalSetProperty("accessId", s3AccessId); service.internalSetProperty("accessKey", s3AccessKey); service.internalSetProperty("bucketName", s3BucketName); - service.internalSetProperty("endpoint", s3BucketEndpoint); service.testProperties(); return service; } From f75c112959bcedcf1045ae425b63095bd254a6a4 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 4 Mar 2015 00:37:56 +0100 Subject: [PATCH 261/264] fixed comment in buildAndUpdateProduct.sh --- configuration/buildAndUpdateProduct.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index a8d771d1529..74de0db3fc7 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -134,7 +134,7 @@ if [ $# -eq 0 ]; then echo "local-deploy: performs deployment of one or more bundles into a local directory" echo "Example: $0 -n com.sap.sailing.www -s /home/user/myserver local-deploy" echo "" - echo "remote-deploy: performs hot deployment of the java code to a remote server" + echo "remote-deploy: deploys the last build results to a remote server and optionally re-starts it" echo "Example: $0 -s dev -w trac@sapsailing.com remote-deploy" echo "" echo "clean: cleans all code and GWT files" From 8182b93bcd2a54c6de05c916dba7623a30965a74 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 4 Mar 2015 08:02:12 +0100 Subject: [PATCH 262/264] adjusted wind estimation from OBMR race to allow for greater variance; wind *was* really shifty that day --- .../TestWindEstimationFromManeuversOnAFew505Races.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/windestimation/TestWindEstimationFromManeuversOnAFew505Races.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/windestimation/TestWindEstimationFromManeuversOnAFew505Races.java index d30eedf5b49..c6f98ebc045 100755 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/windestimation/TestWindEstimationFromManeuversOnAFew505Races.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/windestimation/TestWindEstimationFromManeuversOnAFew505Races.java @@ -82,7 +82,7 @@ public class TestWindEstimationFromManeuversOnAFew505Races extends OnlineTracTra CreateModelException, NotEnoughDataHasBeenAddedException { setUp("OBMR 2012", "event_20121031_OBMR-OBMR_2012_VR_Fr_Fleet_1_2"); Wind average = getManeuverBasedAverageWind(); - assertEquals(222, average.getFrom().getDegrees(), 5.0); + assertEquals(222, average.getFrom().getDegrees(), 20.0); // wind is shifty in this race and goes from 195..245 (measured) } @Test From 0a52e380567b205a910bc19f77234abe8eb4320a Mon Sep 17 00:00:00 2001 From: Samuel Oey Date: Wed, 4 Mar 2015 09:40:45 +0000 Subject: [PATCH 263/264] adds information about the leaderboard integration for the tracking app --- wiki/tracking-app/api-v1.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wiki/tracking-app/api-v1.md b/wiki/tracking-app/api-v1.md index 0d634ea53d0..ed7f776f8ad 100644 --- a/wiki/tracking-app/api-v1.md +++ b/wiki/tracking-app/api-v1.md @@ -268,6 +268,13 @@ $ curl -H "Content-Type:image/jpeg" --data-binary @ \ "teamImageUri" : "http://training.sapsailing.com/team_images/9871d3a2c554b27151cacf1422eec048.jpeg" } ``` +## Leaderboard Integration + +The leaderboard buttons loads the online leaderboard into an web-view. + +``` +http:///gwt/Home.html#RegattaPlace:eventId=&navigationTab=Leaderboard&leaderboardName=&showRaceDetails=false&showSettings=false +``` # Buoy Tender (Tonnenleger) App
      From f2c8cc96b98e54f8cc5dbd78ccacbd7bd9014ea3 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 4 Mar 2015 16:01:19 +0100 Subject: [PATCH 264/264] removed unused field in AmazonS3TestSupport --- .../com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java index 4acad0939ac..6715d79cc62 100644 --- a/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java +++ b/java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java @@ -7,7 +7,6 @@ public class AmazonS3TestSupport { public static final String s3AccessId = "AKIAJOX7PZ6ACI2FQU4A"; public static final String s3AccessKey = "NkijH2DfhWgb9fmESPjpeIbpUF+tC220KyTOfvGJ"; private static final String s3BucketName = "sapsailing-automatic-upload-test"; - private static final String s3BucketEndpoint = "sapsailing-automatic-upload-test.s3-website-eu-west-1.amazonaws.com"; public static AmazonS3FileStorageServiceImpl createService() throws InvalidPropertiesException { AmazonS3FileStorageServiceImpl service = new AmazonS3FileStorageServiceImpl();