Merge branch 'master', remote-tracking branch 'origin/master' into other

This commit is contained in:
Nick Klose committed 2013-12-10 15:49:36 +01:00
commit 68ddd22f44
987 files changed
+26277 -12723

No files matched your search

@@ -4,6 +4,7 @@ import java.io.IOException;
import java.text.ParseException;
import com.sap.sailing.declination.impl.DeclinationServiceImpl;
import com.sap.sailing.declination.impl.NOAAImporter;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Mile;
import com.sap.sailing.domain.common.Position;
@@ -20,11 +21,15 @@ public interface DeclinationService {
/**
* Obtains declination information with the default precision of this declination service in time and space
* dimensions (e.g., one degree N/S and E/W and one year on the time axis). Particularly the time resolution may
* benefit from an extrapolation using the {@link Declination#getAnnualChange() annual change} specification.
* Note that the result is <em>not</em> corrected automatically by the {@link Declination#getAnnualChange()
* annual change} of the result found for the position. The caller may do that using
* benefit from an extrapolation using the {@link Declination#getAnnualChange() annual change} specification. Note
* that the result is <em>not</em> corrected automatically by the {@link Declination#getAnnualChange() annual
* change} of the result found for the position. The caller may do that using
* {@link Declination#getBearingCorrectedTo(TimePoint)}.
* @param timeoutForOnlineFetchInMilliseconds TODO
*
* @param timeoutForOnlineFetchInMilliseconds
* see {@link NOAAImporter#getDeclination(Position, TimePoint, long)}; if 0, waits forever; only needed
* if the pre-stored declinations don't fulfill the request up to the tolerance distance configured when
* constructing this service
*/
Declination getDeclination(TimePoint timePoint, Position position, long timeoutForOnlineFetchInMilliseconds) throws IOException,
ClassNotFoundException, ParseException;
@@ -36,9 +41,13 @@ public interface DeclinationService {
* <p>
*
* The time point precision is the default precision of this service. Note that the result is <em>not</em> corrected
* automatically by the {@link Declination#getAnnualChange() annual change} of the result found for the
* position. The caller may do that using {@link Declination#getBearingCorrectedTo(TimePoint)}.
* @param timeoutForOnlineFetchInMilliseconds TODO
* automatically by the {@link Declination#getAnnualChange() annual change} of the result found for the position.
* The caller may do that using {@link Declination#getBearingCorrectedTo(TimePoint)}.
*
* @param timeoutForOnlineFetchInMilliseconds
* see {@link NOAAImporter#getDeclination(Position, TimePoint, long)}; if 0, waits forever; only needed
* if the pre-stored declinations don't fulfill the request up to the tolerance distance provided by the
* <code>maxDistance</code> parameter
*/
Declination getDeclination(TimePoint timePoint, Position position, Distance maxDistance, long timeoutForOnlineFetchInMilliseconds) throws IOException,
ClassNotFoundException, ParseException;
@@ -7,6 +7,8 @@ Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-ActivationPolicy: lazy
Export-Package: com.sap.sailing.domain.common,
com.sap.sailing.domain.common.configuration,
com.sap.sailing.domain.common.dto,
com.sap.sailing.domain.common.filter,
com.sap.sailing.domain.common.impl,
com.sap.sailing.domain.common.media,
@@ -14,5 +16,4 @@ Export-Package: com.sap.sailing.domain.common,
com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test",
com.sap.sailing.domain.common.racelog,
com.sap.sailing.domain.common.racelog.utils,
com.sap.sailing.domain.common.dto,
com.sap.sailing.domain.common.trackfiles
@@ -13,6 +13,10 @@ public interface Bearing extends Serializable {
*/
Bearing reverse();
/**
* Adds the bearing <code>diff</code> to this bearing and returns the new result. The result will be between 0 and 360 (inclusive) if
* this bearing and <code>diff</code> are also within 0 and 360.
*/
Bearing add(Bearing diff);
/**
@@ -2,9 +2,36 @@ package com.sap.sailing.domain.common;
import java.io.Serializable;
import com.sap.sailing.domain.common.impl.RGBColor;
import com.sap.sailing.domain.common.impl.Util;
public interface Color extends Serializable {
final Color WHITE = new RGBColor(255, 255, 255);
final Color LIGHT_GRAY = new RGBColor(192, 192, 192);
final Color GRAY = new RGBColor(128, 128, 128);
final Color DARK_GRAY = new RGBColor(64, 64, 64);
final Color BLACK = new RGBColor(0, 0, 0);
final Color RED = new RGBColor(255, 0, 0);
final Color PINK = new RGBColor(255, 175, 175);
final Color ORANGE = new RGBColor(255, 200, 0);
final Color YELLOW = new RGBColor(255, 255, 0);
final Color GREEN = new RGBColor(0, 255, 0);
final Color MAGENTA = new RGBColor(255, 0, 255);
final Color CYAN = new RGBColor(0, 255, 255);
final Color BLUE = new RGBColor(0, 0, 255);
Util.Triple<Integer, Integer, Integer> getAsRGB();
Util.Triple<Float, Float, Float> getAsHSV();
@@ -0,0 +1,17 @@
package com.sap.sailing.domain.common;
/**
* Manages color assignments to objects.
*
* @author Axel Uhl (d043530)
*
* @param <T>
* the type of the objects to which a color is assigned
*/
public interface ColorMap<T> {
Color getColorByID(T object);
boolean addBlockedColor(Color color);
boolean removeBlockedColor(Color color);
void clearBlockedColors();
}
@@ -0,0 +1,37 @@
package com.sap.sailing.domain.common;
import java.util.ArrayList;
import java.util.List;
/**
* Used to decided which Course Designer to pick.
*
* When modifying these values also check res/preferences.xml of racecommittee.app!
*/
public enum CourseDesignerMode {
UNKNOWN("Unknown"),
BY_NAME("By-Name Course Designer"), // O2, O3, I2,...
BY_MAP("By-Map Course Designer"), // Map-based Course Designer
BY_MARKS("By-Marks Course Designer"); // ESS
private String displayName;
private CourseDesignerMode(String displayName) {
this.displayName = displayName;
}
@Override
public String toString() {
return displayName;
}
public static CourseDesignerMode[] validValues() {
List<CourseDesignerMode> validValues = new ArrayList<CourseDesignerMode>();
for (CourseDesignerMode type : values()) {
if (type != UNKNOWN) {
validValues.add(type);
}
}
return validValues.toArray(new CourseDesignerMode[0]);
}
}
@@ -29,4 +29,8 @@ public interface SpeedWithBearing extends Speed {
*/
Speed projectTo(Position position, Bearing bearing);
/**
* Adds two directed speeds onto each other using vector addition.
*/
SpeedWithBearing add(SpeedWithBearing other);
}
@@ -0,0 +1,16 @@
package com.sap.sailing.domain.common.configuration;
public enum DeviceConfigurationMatcherType {
SINGLE(1), MULTI(2);
private int matchingRank;
private DeviceConfigurationMatcherType(int rank) {
this.matchingRank = rank;
}
public int getRank() {
return matchingRank;
}
}
@@ -2,6 +2,8 @@ package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import com.sap.sailing.domain.common.Color;
public interface CompetitorDTO extends Serializable {
String getTwoLetterIsoCountryCode();
@@ -17,6 +19,8 @@ public interface CompetitorDTO extends Serializable {
BoatClassDTO getBoatClass();
String getName();
Color getColor();
/**
* A regular instance will simply return this object. A compacted version may compute the result by looking it up
@@ -2,6 +2,8 @@ package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import com.sap.sailing.domain.common.Color;
/**
* Equality and hash code are based on the {@link #getIdAsString() ID}, the {@link #getSailID() sail number}, the
* {@link #getBoatClass() boat class} (whose equality and hash code, in turn, depends on its name) and the
@@ -13,18 +15,20 @@ import java.io.Serializable;
*/
public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serializable {
private static final long serialVersionUID = -4997852354821083154L;
private String countryName;
private String twoLetterIsoCountryCode;
private String threeLetterIocCountryCode;
private String countryName;
private Color color;
private String sailID;
private String idAsString;
private BoatClassDTO boatClass;
CompetitorDTOImpl() {}
public CompetitorDTOImpl(String name, String twoLetterIsoCountryCode, String threeLetterIocCountryCode,
public CompetitorDTOImpl(String name, Color color, String twoLetterIsoCountryCode, String threeLetterIocCountryCode,
String countryName, String sailID, String idAsString, BoatClassDTO boatClass) {
super(name);
this.color = color;
this.twoLetterIsoCountryCode = twoLetterIsoCountryCode;
this.threeLetterIocCountryCode = threeLetterIocCountryCode;
this.countryName = countryName;
@@ -40,6 +44,7 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial
result = prime * result + ((boatClass == null) ? 0 : boatClass.hashCode());
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 + ((threeLetterIocCountryCode == null) ? 0 : threeLetterIocCountryCode.hashCode());
return result;
}
@@ -73,6 +78,11 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial
return false;
} else if (!threeLetterIocCountryCode.equals(other.threeLetterIocCountryCode))
return false;
if (color == null) {
if (other.color != null)
return false;
} else if (!color.equals(other.color))
return false;
return true;
}
@@ -111,4 +121,7 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial
return this;
}
public Color getColor() {
return color;
}
}
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common.dto;
import com.sap.sailing.domain.common.Color;
/**
* Equality and hash code are based on the index pointing into a previous leaderboard's competitors list.
*
@@ -67,6 +69,12 @@ public class PreviousCompetitorDTOImpl implements CompetitorDTO {
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public Color getColor() {
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 getIdAsString() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
@@ -0,0 +1,23 @@
package com.sap.sailing.domain.common.impl;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.impl.Util.Triple;
public abstract class AbstractColor implements Color {
private static final long serialVersionUID = 7758884012281863458L;
@Override
public String getAsHtml() {
Triple<Integer, Integer, Integer> asRGB = getAsRGB();
return "#" + toBrowserHexValue(asRGB.getA()) + toBrowserHexValue(asRGB.getB())
+ toBrowserHexValue(asRGB.getC());
}
private static String toBrowserHexValue(int number) {
StringBuilder builder = new StringBuilder(Integer.toHexString(number & 0xff));
while (builder.length() < 2) {
builder.append("0");
}
return builder.toString().toUpperCase();
}
}
@@ -33,6 +33,11 @@ public abstract class AbstractSpeedWithAbstractBearingImpl extends AbstractSpeed
&& getBearing().equals(((SpeedWithBearing) object).getBearing());
}
@Override
public SpeedWithBearing add(SpeedWithBearing other) {
return AbstractSpeedWithAbstractBearingImpl.add(this, other);
}
@Override
public CourseChange getCourseChangeRequiredToReach(SpeedWithBearing targetSpeedWithBearing) {
return AbstractSpeedWithBearingImpl.getCourseChangeRequiredToReach(this, targetSpeedWithBearing);
@@ -80,4 +85,12 @@ public abstract class AbstractSpeedWithAbstractBearingImpl extends AbstractSpeed
public Speed projectTo(Position position, Bearing projectTo) {
return projectTo(this, position, projectTo);
}
public static SpeedWithBearing add(SpeedWithBearing first, SpeedWithBearing other) {
double x = first.getMetersPerSecond()*Math.cos(first.getBearing().getRadians()) + other.getMetersPerSecond()*Math.cos(other.getBearing().getRadians());
double y = first.getMetersPerSecond()*Math.sin(first.getBearing().getRadians()) + other.getMetersPerSecond()*Math.sin(other.getBearing().getRadians());
double metersPerSecond = Math.sqrt(x*x+y*y);
double directionRad = (360.+Math.atan2(y, x))%360.;
return new MeterPerSecondSpeedWithDegreeBearingImpl(metersPerSecond, new RadianBearingImpl(directionRad));
}
}
@@ -15,5 +15,4 @@ public abstract class AbstractSpeedWithBearingImpl extends AbstractSpeedWithAbst
public Bearing getBearing() {
return bearing;
}
}
@@ -1,8 +1,13 @@
package com.sap.sailing.gwt.ui.client;
package com.sap.sailing.domain.common.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.ColorMap;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.common.impl.HSVColor;
/**
* Manages color assignments to objects.
@@ -12,14 +17,17 @@ import com.sap.sailing.domain.common.impl.HSVColor;
* @param <T>
* the type of the objects to which a color is assigned
*/
public class ColorMap<T> {
private final HashMap<T, String> idColor;
public class ColorMapImpl<T> implements ColorMap<T> {
private final HashMap<T, Color> idColor;
private int colorCounter;
private HSVColor[] baseColors;
public ColorMap() {
/** a list of already used colors which should be excluded from the automatic color assignment */
private List<HSVColor> blockedColors;
public ColorMapImpl() {
baseColors = new HSVColor[10];
baseColors[0] = new HSVColor(0.0f, 1.0f, 1.0f); // Red
baseColors[1] = new HSVColor(30.0f, 1.0f, 1.0f); // Orange
@@ -32,7 +40,8 @@ public class ColorMap<T> {
baseColors[8] = new HSVColor(300.0f, 1.0f, 1.0f); // Magenta
baseColors[9] = new HSVColor(330.0f, 1.0f, 1.0f);
idColor = new HashMap<T, String>();
idColor = new HashMap<T, Color>();
blockedColors = new ArrayList<HSVColor>();
}
/**
@@ -43,15 +52,37 @@ public class ColorMap<T> {
* object's {@link Object#equals(Object)} and {@link Object#hashCode()}.
* @return A color in hex/html-format (e.g. #ff0000)
*/
public String getColorByID(T object) {
String color = idColor.get(object);
if (color == null || color.isEmpty()) {
public Color getColorByID(T object) {
Color color = idColor.get(object);
if (color == null) {
color = createHexColor(colorCounter++);
idColor.put(object, color);
}
return color;
}
public boolean addBlockedColor(Color color) {
boolean result = false;
if(color != null) {
Triple<Float, Float, Float> asHSV = color.getAsHSV();
result = blockedColors.add(new HSVColor(asHSV.getA(), asHSV.getB(), asHSV.getC()));
}
return result;
}
public boolean removeBlockedColor(Color color) {
boolean result = false;
if(color != null) {
Triple<Float, Float, Float> asHSV = color.getAsHSV();
result = blockedColors.remove(new HSVColor(asHSV.getA(), asHSV.getB(), asHSV.getC()));
}
return result;
}
public void clearBlockedColors() {
blockedColors.clear();
}
/**
* Only use this if you don't want the color to be cached.
*
@@ -59,7 +90,7 @@ public class ColorMap<T> {
* The index of e.g. a competitor. Make sure, that each competitor has a unique index.
* @return A color computed using the {@code index}.
*/
private String createHexColor(int index) {
private Color createHexColor(int index) {
int baseColorCount = baseColors.length;
int baseColorsIndex = index % baseColorCount;
int factor = index / (baseColorCount*3);
@@ -87,6 +118,6 @@ public class ColorMap<T> {
HSVColor hsvColor = baseColors[baseColorsIndex];
HSVColor newColor = new HSVColor(hsvColor.getHue(), hsvColor.getSaturation() - saturationDecrease,
hsvColor.getBrightness() - brightnessDecrease);
return newColor.getAsHtml();
return newColor;
}
}
@@ -1,6 +1,5 @@
package com.sap.sailing.domain.common.impl;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.impl.Util.Triple;
/**
@@ -9,12 +8,12 @@ import com.sap.sailing.domain.common.impl.Util.Triple;
*
* @author Frank
*/
public class HSVColor implements Color {
public class HSVColor extends AbstractColor {
private static final long serialVersionUID = 7602013229606352246L;
float hue;
float saturation;
float brightness;
protected float hue;
protected float saturation;
protected float brightness;
HSVColor() {
} // for GWT serializability
@@ -86,21 +85,6 @@ public class HSVColor implements Color {
return RGBColor;
}
@Override
public String getAsHtml() {
Triple<Integer, Integer, Integer> asRGB = getAsRGB();
return "#" + toBrowserHexValue(asRGB.getA()) + toBrowserHexValue(asRGB.getB())
+ toBrowserHexValue(asRGB.getC());
}
private static String toBrowserHexValue(int number) {
StringBuilder builder = new StringBuilder(Integer.toHexString(number & 0xff));
while (builder.length() < 2) {
builder.append("0");
}
return builder.toString().toUpperCase();
}
@Override
public Triple<Float, Float, Float> getAsHSV() {
return new Triple<Float, Float, Float>(hue, saturation, brightness);
@@ -122,4 +106,32 @@ public class HSVColor implements Color {
public float getBrightness() {
return brightness;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Float.floatToIntBits(brightness);
result = prime * result + Float.floatToIntBits(hue);
result = prime * result + Float.floatToIntBits(saturation);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
HSVColor other = (HSVColor) obj;
if (Float.floatToIntBits(brightness) != Float.floatToIntBits(other.brightness))
return false;
if (Float.floatToIntBits(hue) != Float.floatToIntBits(other.hue))
return false;
if (Float.floatToIntBits(saturation) != Float.floatToIntBits(other.saturation))
return false;
return true;
}
}
@@ -0,0 +1,31 @@
package com.sap.sailing.domain.common.impl;
import com.sap.sailing.domain.common.impl.Util.Pair;
public class QRCodeUtils {
private static final String fragmentKey = "identifier";
public static String composeQRContent(String deviceIdentifier, String apkUrl) {
// poor man's uri fragment encoding: ' ' as '%20'
String encodedIdentifier = deviceIdentifier.replaceAll(" ", "%20");
return apkUrl + "#" + fragmentKey + "=" + encodedIdentifier;
}
public static Pair<String, String> splitQRContent(String qrCodeContent) {
int fragmentIndex = qrCodeContent.lastIndexOf('#');
if (fragmentIndex == -1 || fragmentIndex == 0 || qrCodeContent.length() == fragmentIndex + 1) {
throw new IllegalArgumentException("There is no server or identifier.");
}
String deviceIdentifier = qrCodeContent.substring(fragmentIndex + 1, qrCodeContent.length());
if (!deviceIdentifier.startsWith(fragmentKey + "=")) {
throw new IllegalArgumentException("The identifier is malformed");
}
deviceIdentifier = deviceIdentifier.substring((fragmentKey + "=").length());
deviceIdentifier = deviceIdentifier.replaceAll("%20", " ");
String apkUrl = qrCodeContent.substring(0, fragmentIndex);
return new Pair<String, String>(deviceIdentifier, apkUrl);
}
}
@@ -1,13 +1,12 @@
package com.sap.sailing.domain.common.impl;
import com.sap.sailing.domain.common.Color;
import com.sap.sailing.domain.common.impl.Util.Triple;
/**
* A color defined in the RGB color schema
* @author Frank
*/
public class RGBColor implements Color {
public class RGBColor extends AbstractColor {
private static final long serialVersionUID = -4091876840771631308L;
private int red;
private int green;
@@ -21,6 +20,40 @@ public class RGBColor implements Color {
this.green = ensureValidRange(green);
this.blue = ensureValidRange(blue);
}
/**
* Constructs an RGB color from a string such as "#FF00FF"
*/
public RGBColor(String htmlColor) {
if (htmlColor.startsWith("#")) {
htmlColor = htmlColor.substring(1);
}
if (htmlColor.endsWith(";")) {
htmlColor = htmlColor.substring(0, htmlColor.length() - 1);
}
int r, g, b;
switch (htmlColor.length()) {
case 6:
r = Integer.parseInt(htmlColor.substring(0, 2), 16);
g = Integer.parseInt(htmlColor.substring(2, 4), 16);
b = Integer.parseInt(htmlColor.substring(4, 6), 16);
break;
case 3:
r = Integer.parseInt(htmlColor.substring(0, 1), 16);
g = Integer.parseInt(htmlColor.substring(1, 2), 16);
b = Integer.parseInt(htmlColor.substring(2, 3), 16);
break;
case 1:
r = g = b = Integer.parseInt(htmlColor.substring(0, 1), 16);
break;
default:
throw new IllegalArgumentException(htmlColor);
}
this.red = ensureValidRange(r);
this.green = ensureValidRange(g);
this.blue = ensureValidRange(b);
}
private int ensureValidRange(int value) {
int result = value;
@@ -78,19 +111,6 @@ public class RGBColor implements Color {
return HSVColor;
}
@Override
public String getAsHtml() {
return "#" + toBrowserHexValue(red) + toBrowserHexValue(green) + toBrowserHexValue(blue);
}
private static String toBrowserHexValue(int number) {
StringBuilder builder = new StringBuilder(Integer.toHexString(number & 0xff));
while (builder.length() < 2) {
builder.append("0");
}
return builder.toString().toUpperCase();
}
@Override
public int hashCode() {
final int prime = 31;
@@ -0,0 +1,37 @@
package com.sap.sailing.domain.common.racelog;
public class FlagPole {
private final Flags upperFlag;
private final Flags lowerFlag;
private final boolean isDisplayed;
public FlagPole(Flags flag, boolean isDisplayed) {
this(flag, Flags.NONE, isDisplayed);
}
public FlagPole(Flags upperFlag, Flags lowerFlag, boolean isDisplayed) {
this.upperFlag = upperFlag;
this.lowerFlag = lowerFlag;
this.isDisplayed = isDisplayed;
}
public Flags getUpperFlag() {
return upperFlag;
}
public Flags getLowerFlag() {
return lowerFlag;
}
public boolean isDisplayed() {
return isDisplayed;
}
public boolean describesSame(FlagPole otherPole) {
return this.upperFlag == otherPole.upperFlag &&
this.lowerFlag == otherPole.lowerFlag &&
this.isDisplayed == otherPole.isDisplayed;
}
}
@@ -1,5 +1,8 @@
package com.sap.sailing.domain.common.racelog;
import java.util.ArrayList;
import java.util.List;
public enum Flags {
NONE,
ALPHA,
@@ -20,5 +23,15 @@ public enum Flags {
AP,
ESSONE,
ESSTWO,
ESSTHREE
ESSTHREE;
public static Flags[] validValues() {
List<Flags> validValues = new ArrayList<Flags>();
for (Flags flag : values()) {
if (flag != NONE) {
validValues.add(flag);
}
}
return validValues.toArray(new Flags[validValues.size()]);
}
}
@@ -0,0 +1,38 @@
package com.sap.sailing.domain.common.racelog;
import java.util.ArrayList;
import java.util.List;
/**
* Used to identify a RacingProcedure's type.
*
* When modifying these values also check res/preferences.xml of racecommittee.app!
*/
public enum RacingProcedureType {
UNKNOWN("Unknown"),
RRS26("Fix Line Start (RRS26)"),
GateStart("Gate Start"),
ESS("\"Extreme Sailing Series\"-Start"),
BASIC("Basic Countdown Start");
private String displayName;
private RacingProcedureType(String displayName) {
this.displayName = displayName;
}
@Override
public String toString() {
return displayName;
}
public static RacingProcedureType[] validValues() {
List<RacingProcedureType> validValues = new ArrayList<RacingProcedureType>();
for (RacingProcedureType type : values()) {
if (type != UNKNOWN) {
validValues.add(type);
}
}
return validValues.toArray(new RacingProcedureType[0]);
}
}
@@ -1,18 +0,0 @@
package com.sap.sailing.domain.common.racelog;
public enum StartProcedureType {
RRS26("Fix Line Start (RRS26)"),
GateStart("Gate Start"),
ESS("\"Extreme Sailing Series\"-Start");
private String displayName;
private StartProcedureType(String displayName) {
this.displayName = displayName;
}
@Override
public String toString() {
return displayName;
}
}
@@ -2,7 +2,6 @@ package com.sap.sailing.domain.igtimiadapter.persistence.impl;
import java.util.ArrayList;
import java.util.List;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
@@ -58,20 +58,34 @@ public class FixFactoryTest {
"},\n"+
"\"13:100\": {\n"+
"\"1\": [\n"+
"60\n"+
"23766\n"+
"24619\n"+
"],\n"+
"\"2\": [\n"+
"105\n"+
"106\n"+
"],\n"+
"\"3\": [\n"+
"72\n"+
"72\n"+
"],\n"+
"\"t\": [\n"+
"12345678000\n"+
"1385628380500\n"+
"1385628381000\n"+
"]\n"+
"},\n"+
"\"13:201\": {\n"+
"\"1\": [\n"+
"119,\n"+
"120\n"+
"24619\n"+
"],\n"+
"\"2\": [\n"+
"106\n"+
"],\n"+
"\"3\": [\n"+
"119\n"+
"],\n"+
"\"t\": [\n"+
"12345677000,\n"+
"12345677500\n"+
"1385628381000\n"+
"]\n"+
"}\n"+
"}\n"+
@@ -102,7 +116,7 @@ public class FixFactoryTest {
assertEquals(21, satCount.getSatCount());
fixIter.next();
AntHrm hrm = (AntHrm) fixIter.next();
assertEquals("100", hrm.getSensor().getDeviceId());
assertEquals(60, hrm.getHeartRate());
assertEquals(100, hrm.getSensor().getSensorId());
assertEquals(72, hrm.getHeartRate());
}
}
@@ -11,7 +11,6 @@ import org.json.simple.parser.ParseException;
import org.junit.Test;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
import com.sap.sailing.mongodb.MongoDBConfiguration;
import com.sap.sailing.mongodb.MongoDBService;
@@ -23,7 +22,7 @@ public class WebSocketServerListTest {
"aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback");
MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration();
MongoDBService mongoTestService = mongoTestConfig.getService();
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
final IgtimiConnectionFactoryImpl igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
Iterable<URI> serverUris = igtimiConnectionFactory.getWebsocketServers();
assertFalse(Util.isEmpty(serverUris));
@@ -0,0 +1,69 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWA;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWS;
import com.sap.sailing.domain.igtimiadapter.datatypes.COG;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong;
import com.sap.sailing.domain.igtimiadapter.datatypes.HDG;
import com.sap.sailing.domain.igtimiadapter.datatypes.SOG;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindListener;
public class WindReceiverTest {
@Test
public void simpleWindReceiverTest() {
final List<Wind> windReceived = new ArrayList<>();
final String deviceSerialNumber = "Non-Existing Test Device";
IgtimiWindReceiver receiver = new IgtimiWindReceiver(deviceSerialNumber);
receiver.addListener(new WindListener() {
@Override
public void windDataReceived(Wind wind) {
windReceived.add(wind);
}
@Override
public void windDataRemoved(Wind wind) {}
@Override
public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {}
});
TimePoint timePoint = MillisecondsTimePoint.now();
Map<Integer, Object> awaMap = new HashMap<>(); awaMap.put(1, 123. /* degrees from */);
Map<Integer, Object> awsMap = new HashMap<>(); awsMap.put(1, 12. /* knots */);
Map<Integer, Object> hdgMap = new HashMap<>(); hdgMap.put(1, 20. /* true degrees */);
Map<Integer, Object> gpsLatLongMap = new HashMap<>(); gpsLatLongMap.put(2, 49.8 /* lat */); gpsLatLongMap.put(1, 8.93 /* lng */);
Map<Integer, Object> sogMap = new HashMap<>(); sogMap.put(1, 10. /* knots */);
Map<Integer, Object> cogMap = new HashMap<>(); cogMap.put(1, 22. /* degrees; 2 degrees drift */);
final SensorImpl sensor = new SensorImpl(deviceSerialNumber, 0);
SpeedWithBearing boatSogCog = new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(22));
SpeedWithBearing apparentWind = new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(20).add(new DegreeBearingImpl(123).reverse()));
SpeedWithBearing expectedTrueWind = apparentWind.add(boatSogCog);
receiver.received(Arrays.asList(new AWA(timePoint, sensor, awaMap),
new AWS(timePoint, sensor, awsMap),
new HDG(timePoint, sensor, hdgMap),
new GpsLatLong(timePoint, sensor, gpsLatLongMap),
new SOG(timePoint, sensor, sogMap),
new COG(timePoint, sensor, cogMap)));
assertFalse(windReceived.isEmpty());
assertEquals(1, windReceived.size());
Wind wind = windReceived.get(0);
assertEquals(49.8, wind.getPosition().getLatDeg(), 0.00000001);
assertEquals(8.93, wind.getPosition().getLngDeg(), 0.00000001);
assertEquals(expectedTrueWind.getKnots(), wind.getKnots(), 0.00000001);
assertEquals(expectedTrueWind.getBearing().getDegrees(), wind.getBearing().getDegrees(), 0.00000001);
}
}
@@ -3,12 +3,8 @@ package com.sap.sailing.domain.igtimiadapter.oauth;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
@@ -18,17 +14,9 @@ import org.json.simple.parser.ParseException;
import org.junit.Test;
import org.xml.sax.SAXException;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.Client;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.Resource;
import com.sap.sailing.domain.igtimiadapter.User;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
import com.sap.sailing.domain.igtimiadapter.impl.Activator;
import com.sap.sailing.domain.igtimiadapter.impl.ClientImpl;
import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl;
@@ -48,7 +36,7 @@ public class SignInTest {
"aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback");
MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration();
MongoDBService mongoTestService = mongoTestConfig.getService();
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
final IgtimiConnectionFactoryImpl igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
final String code = igtimiConnectionFactory.authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456");
logger.info("Igtimi OAuth code is "+code);
@@ -66,51 +54,35 @@ public class SignInTest {
"4d66022d1ec3e2991f8053514495b61cc076ff02d664f0dc8f3df9150c3864ef", "http://1.2.3.4");
MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration();
MongoDBService mongoTestService = mongoTestConfig.getService();
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
final IgtimiConnectionFactoryImpl igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
final String code = igtimiConnectionFactory.authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456");
logger.info("Igtimi OAuth code is "+code);
assertNotNull(code);
}
@Test
public void testAuthorize() throws ClientProtocolException, IOException,
IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException,
InstantiationException, IllegalAccessException, ClassCastException, ParseException {
// use the credentials of "Another Test App"
final Client testAppClient = new ClientImpl("a4cecd8593e12d43a03433a6db0eea243a411749f93c278dce6a26d4804eebd2",
"4d66022d1ec3e2991f8053514495b61cc076ff02d664f0dc8f3df9150c3864ef", "http://1.2.3.4");
MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration();
MongoDBService mongoTestService = mongoTestConfig.getService();
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
final Account account = igtimiConnectionFactory.createAccountToAccessUserData("axel.uhl@gmx.de", "123456");
assertNotNull(account);
logger.info("Igtimi account is "+account);
}
@Test
public void testAddToken() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
assertEquals("axel.uhl@gmx.de", account.getUser().getEmail());
assertSame(account, connectionFactory.getAccountByEmail("axel.uhl@gmx.de"));
assertSame(account, connectionFactory.getExistingAccountByEmail("axel.uhl@gmx.de"));
}
@Test
public void testGetUsers() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
IgtimiConnection connection = connectionFactory.connect(account);
Iterable<User> users = connection.getUsers();
assertEquals(1, Util.size(users));
assertEquals(account.getUser().getId(), users.iterator().next().getId());
}
@Test
public void testGetResources() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
IgtimiConnection connection = connectionFactory.connect(account);
Iterable<Resource> resources = connection.getResources(Permission.read, /* start time */ null, /* end time */ null,
/* serial numbers */ Collections.singleton("GA-EN-AAEJ"), /* stream IDs */ null);
assertTrue(resources.iterator().hasNext());
}
@Test
public void testGetResourceData() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
IgtimiConnection connection = connectionFactory.connect(account);
Map<Type, Double> typesAndCompression = new HashMap<>();
typesAndCompression.put(Type.gps_latlong, 0.0);
Iterable<Fix> data = connection.getResourceData(new MillisecondsTimePoint(1384420883000l),
new MillisecondsTimePoint(1384421639000l), Collections.singleton("GA-EN-AAEJ"), typesAndCompression);
assertTrue(data.iterator().hasNext());
}
}
@@ -0,0 +1,23 @@
package com.sap.sailing.domain.igtimiadapter.test;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.junit.Before;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.impl.Activator;
public class AbstractTestWithIgtimiConnection {
protected IgtimiConnection connection;
@Before
public void setUp() throws ClientProtocolException, IOException, org.json.simple.parser.ParseException {
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
connection = connectionFactory.connect(account);
}
}
@@ -0,0 +1,135 @@
package com.sap.sailing.domain.igtimiadapter.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import org.junit.Test;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.DataAccessWindow;
import com.sap.sailing.domain.igtimiadapter.Group;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.Resource;
import com.sap.sailing.domain.igtimiadapter.Session;
import com.sap.sailing.domain.igtimiadapter.User;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWA;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWS;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
import com.sap.sailing.domain.igtimiadapter.impl.Activator;
public class BasicIgtimiAdapterTest extends AbstractTestWithIgtimiConnection {
@Test
public void testGetUsers() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Iterable<User> users = connection.getUsers();
assertEquals(1, Util.size(users));
assertEquals(connection.getAccount().getUser().getId(), users.iterator().next().getId());
}
@Test
public void testGetGroups() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Iterable<Group> groups = connection.getGroups();
assertTrue(Util.size(groups) > 1);
}
@Test
public void testGetDataAccessWindows() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Iterable<DataAccessWindow> daws = connection.getDataAccessWindows(Permission.read, /* startTime */ null,
/* endTime */ null, /* deviceSerialNumbers */ Collections.singleton("DD-EE-AAGA"));
assertFalse(Util.isEmpty(daws));
for (DataAccessWindow daw : daws) {
assertEquals("DD-EE-AAGA", daw.getDeviceSerialNumber());
assertTrue(daw.getRecipient() instanceof Group);
}
}
@Test
public void testGetAllDataAccessWindows() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Iterable<DataAccessWindow> daws = connection.getDataAccessWindows(Permission.read, /* startTime */ null,
/* endTime */ null, /* deviceSerialNumbers */ null);
assertFalse(Util.isEmpty(daws));
}
@Test
public void testGetResources() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Iterable<Resource> resources = connection.getResources(Permission.read, /* start time */ null, /* end time */ null,
/* serial numbers */ Collections.singleton("GA-EN-AAEJ"), /* stream IDs */ null);
assertTrue(resources.iterator().hasNext());
}
@Test
public void testGetSessions() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Iterable<Session> sessions = connection.getSessions(Collections.singleton(4571l), /* isPublic */ null, /* limit */ 1, /* includeIncomplete */ null);
assertTrue(sessions.iterator().hasNext());
final Session session = sessions.iterator().next();
assertEquals("Auto Session - 01:14:02 10 Nov", session.getName());
User sessionOwher = connection.getUser(session.getOwnerId());
assertEquals("Uhl", sessionOwher.getSurname());
}
@Test
public void testGetResourceData() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
Map<Type, Double> typesAndCompression = new HashMap<>();
typesAndCompression.put(Type.gps_latlong, 0.0);
Iterable<Fix> data = connection.getResourceData(new MillisecondsTimePoint(1384420883000l),
new MillisecondsTimePoint(1384421639000l), Collections.singleton("GA-EN-AAEJ"), typesAndCompression);
assertTrue(data.iterator().hasNext());
}
@Test
public void testDataAccessWindowForGivenTimeFrame() throws java.text.ParseException, IllegalStateException, ClientProtocolException, IOException, ParseException {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.GERMAN);
TimePoint start = new MillisecondsTimePoint(dateFormat.parse("2013-11-07T08:00:00Z"));
TimePoint end = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T18:00:00Z"));
// URL is https://www.igtimi.com/api/v1/devices/data_access_windows?type=read&start_time=1383811200000&end_time=1383933600000&access_token=3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9
Iterable<DataAccessWindow> daws = connection.getDataAccessWindows(Permission.read, start, end, /* deviceSerialNumbers; get all devices available for that time */ null);
assertFalse(Util.isEmpty(daws));
for (DataAccessWindow daw : daws) {
assertTrue((daw.getStartTime().compareTo(start)<=0 && daw.getEndTime().compareTo(start)>0) // spans start
|| (daw.getStartTime().compareTo(end)<=0 && daw.getEndTime().compareTo(end)>0) // spans end
|| (daw.getStartTime().compareTo(start)>=0 && daw.getEndTime().compareTo(end)<=0)); // lies within
}
}
@Test
public void testResourceDataForGivenTimeFrame() throws java.text.ParseException, IllegalStateException, ClientProtocolException, IOException, ParseException {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.GERMAN);
TimePoint start = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:00:00Z"));
TimePoint end = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:10:00Z"));
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
IgtimiConnection connection = connectionFactory.connect(account);
// URL is https://www.igtimi.com/api/v1/devices/data_access_windows?type=read&start_time=1383811200000&end_time=1383933600000&access_token=3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9
Iterable<DataAccessWindow> daws = connection.getDataAccessWindows(Permission.read, start, end, /* deviceSerialNumbers; get all devices available for that time */ null);
Set<String> deviceSerialNumbers = new HashSet<>();
for (DataAccessWindow daw : daws) {
deviceSerialNumbers.add(daw.getDeviceSerialNumber());
}
Iterable<Fix> windData = connection.getResourceData(start, end, deviceSerialNumbers, Type.gps_latlong, Type.AWA, Type.AWS, Type.HDG);
assertFalse(Util.isEmpty(windData));
boolean foundWind = false;
for (Fix fix : windData) {
foundWind = foundWind || fix instanceof AWA || fix instanceof AWS;
assertTrue(fix.getTimePoint().compareTo(start) >= 0 && fix.getTimePoint().compareTo(end) <= 0);
}
assertTrue(foundWind);
}
}
@@ -0,0 +1,75 @@
package com.sap.sailing.domain.igtimiadapter.test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.http.client.ClientProtocolException;
import org.junit.Test;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.igtimiadapter.DataAccessWindow;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.Wind;
/**
* Igtimi fixes come as isolated single fixes from separate sensors, and even if they are attached to the same device,
* their time stamps may not be synchronized. Therefore, separate {@link Track} tracks will be used to hold the data
* coming from the different sensors attached to the same device, and several of them need to be joined to allow for the
* construction, e.g., of a single {@link Wind} of {@link GPSFixMoving} fix.
*
* @author Axel Uhl (d043530)
*
*/
public class IgtimiFixTrackTest extends AbstractTestWithIgtimiConnection {
@Test
public void testFetchFixesIntoTracks() throws ParseException, ClientProtocolException, IllegalStateException, IOException, org.json.simple.parser.ParseException {
setUp();
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.GERMAN);
TimePoint start = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:00:00Z"));
TimePoint end = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:10:00Z"));
// URL is https://www.igtimi.com/api/v1/devices/data_access_windows?type=read&start_time=1383811200000&end_time=1383933600000&access_token=3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9
Iterable<DataAccessWindow> daws = connection.getDataAccessWindows(Permission.read, start, end, /* deviceSerialNumbers; get all devices available for that time */ null);
Set<String> deviceSerialNumbers = new HashSet<>();
for (DataAccessWindow daw : daws) {
deviceSerialNumbers.add(daw.getDeviceSerialNumber());
}
Map<String, Map<Type, DynamicTrack<Fix>>> data = connection.getResourceDataAsTracks(start, end, deviceSerialNumbers, Type.gps_latlong, Type.AWA, Type.AWS, Type.HDG, Type.HDGM);
assertFalse(data.isEmpty());
Map<Type, DynamicTrack<Fix>> windSensorMap = data.get("DD-EE-AAHG");
assertNotNull(windSensorMap);
assertTrue(windSensorMap.containsKey(Type.AWA));
assertTrue(windSensorMap.containsKey(Type.AWS));
assertTrue(windSensorMap.containsKey(Type.HDGM));
final DynamicTrack<Fix> awaTrack = windSensorMap.get(Type.AWA);
assertFalse(isEmpty(awaTrack));
final DynamicTrack<Fix> awsTrack = windSensorMap.get(Type.AWS);
assertFalse(isEmpty(awsTrack));
final DynamicTrack<Fix> hdgmTrack = windSensorMap.get(Type.HDGM);
assertFalse(isEmpty(hdgmTrack));
}
private boolean isEmpty(final DynamicTrack<Fix> track) {
track.lockForRead();
try {
return Util.isEmpty(track.getRawFixes());
} finally {
track.unlockAfterRead();
}
}
}
@@ -28,9 +28,14 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.junit.Test;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Client;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.LiveDataConnection;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.impl.ClientImpl;
import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl;
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
@@ -142,6 +147,7 @@ public class WebSocketTest {
@Test
public void testWebSocketConnect() throws Exception {
final List<Fix> allFixesReceived = new ArrayList<>();
final Client client = new ClientImpl("7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6",
"aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback");
MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration();
@@ -149,9 +155,16 @@ public class WebSocketTest {
final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(client, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService),
PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService));
Account account = igtimiConnectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
WebSocketConnectionManager manager = new WebSocketConnectionManager(igtimiConnectionFactory, Collections.singleton("GA-EN-AAEJ"), account);
assertNotNull(manager);
assertTrue("Connection handshake not successful within 5s", manager.waitForConnection(5000));
manager.disconnect();
IgtimiConnection conn = igtimiConnectionFactory.connect(account);
LiveDataConnection liveDataConnection = conn.createLiveConnection(Collections.singleton("GA-EN-AAEJ"));
liveDataConnection.addListener(new BulkFixReceiver() {
@Override
public void received(Iterable<Fix> fixes) {
Util.addAll(fixes, allFixesReceived);
}
});
assertNotNull(liveDataConnection);
assertTrue("Connection handshake not successful within 5s", liveDataConnection.waitForConnection(5000l));
liveDataConnection.disconnect();
}
}
@@ -12,7 +12,10 @@ Require-Bundle: org.json.simple;bundle-version="1.1.0",
org.apache.httpcomponents.httpcore;bundle-version="4.0.0",
org.apache.commons.logging;bundle-version="1.1.1",
com.sap.sailing.domain.common,
com.sap.sailing.domain.igtimiadapter.persistence
com.sap.sailing.domain.igtimiadapter.persistence,
com.sap.sailing.domain.shared.android,
com.sap.sailing.domain,
com.sap.sailing.declination
Import-Package: javax.ws.rs;version="1.1.1",
javax.ws.rs.core;version="1.1.1",
javax.ws.rs.ext;version="1.1.1",
@@ -27,3 +30,5 @@ Import-Package: javax.ws.rs;version="1.1.1",
org.osgi.framework;version="1.7.0"
Web-ContextPath: /igtimi
Bundle-Activator: com.sap.sailing.domain.igtimiadapter.impl.Activator
Export-Package: com.sap.sailing.domain.igtimiadapter,
com.sap.sailing.domain.igtimiadapter.datatypes
@@ -0,0 +1,15 @@
package com.sap.sailing.domain.igtimiadapter;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
/**
* Allows listeners to receive a batch of fixes that the connector received in one transaction. This will usually
* improve chances that sensor readings belonging together (such as an AWA and an AWS fix) will be contained in the
* same batch.
*
* @author Axel Uhl (d043530)
*
*/
public interface BulkFixReceiver {
void received(Iterable<Fix> fixes);
}
@@ -0,0 +1,22 @@
package com.sap.sailing.domain.igtimiadapter;
/**
* When a device is shared to you, all that happens is that a DAW (Data Access Window) is being created for the time
* interval that the device owner wants you to have access to the data from the device.<p>
*
* So you’re not actually sharing a device per-se, rather you are granting access to data that it creates. This means
* that you don’t see shared devices in the devices::list call – that’s only for devices that you own at the instant in
* time the call is made. DAW’s allow granting access to data from a device for any interval in time; past, present or
* future.<p>
*
* So if you need to get a complete list of serials that you can request data for, whether or not you are the owner,
* then you need to use the data_access_windows:: call.<p>
*
* @author Axel Uhl (D043530)
*
*/
public interface DataAccessWindow extends HasStartAndEndTime, HasPermissions {
String getDeviceSerialNumber();
SecurityEntity getRecipient();
}
@@ -0,0 +1,29 @@
package com.sap.sailing.domain.igtimiadapter;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
public interface Device extends HasId {
/**
* @return a string identifying the device, such as "AA-AA-AAAA"
*/
String getSerialNumber();
String getServiceTag();
Long getOwnerId();
Long getDeviceUserGroupId();
Long getAdminDeviceUserGroupId();
Iterable<Permission> getPermissions();
Boolean getBlob();
String getName();
User getOwner() throws IllegalStateException, ClientProtocolException, IOException, ParseException;
}
@@ -0,0 +1,25 @@
package com.sap.sailing.domain.igtimiadapter;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
/**
* Notifies a {@link IgtimiFixReceiver receiver} about each fix received.
*
* @author Axel Uhl (d043530)
*
*/
public class FixVisitor implements BulkFixReceiver {
private final IgtimiFixReceiver receiver;
public FixVisitor(IgtimiFixReceiver receiver) {
this.receiver = receiver;
}
@Override
public void received(Iterable<Fix> fixes) {
for (Fix fix : fixes) {
fix.notify(receiver);
}
}
}
@@ -0,0 +1,11 @@
package com.sap.sailing.domain.igtimiadapter;
public interface Group extends SecurityEntity {
String getName();
Boolean isHidden();
Iterable<Permission> getPermissions();
Boolean isBlob();
}
@@ -0,0 +1,5 @@
package com.sap.sailing.domain.igtimiadapter;
public interface HasId {
long getId();
}
@@ -0,0 +1,5 @@
package com.sap.sailing.domain.igtimiadapter;
public interface HasPermissions {
Iterable<Permission> getPermissions();
}
@@ -0,0 +1,9 @@
package com.sap.sailing.domain.igtimiadapter;
import com.sap.sailing.domain.common.TimePoint;
public interface HasStartAndEndTime {
TimePoint getEndTime();
TimePoint getStartTime();
}
@@ -9,6 +9,8 @@ import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.Track;
/**
* A connection to the Igtimi system for one {@link Client} and one {@link Account}.
@@ -19,6 +21,8 @@ public interface IgtimiConnection {
Iterable<User> getUsers() throws IllegalStateException, ClientProtocolException, IOException, ParseException;
User getUser(long id) throws IllegalStateException, ClientProtocolException, IOException, ParseException;
/**
* @param startTime
* optional; may be <code>null</code>
@@ -36,9 +40,105 @@ public interface IgtimiConnection {
/**
* All arguments are mandatory.
*
* @param deviceSerialNumbers
* the serial numbers of the devices for which to return data; these numbers can be obtained, e.g., from
* {@link #getOwnedDevices()}.{@link Device#getSerialNumber() getSerialNumber()} or from
* {@link #getDataAccessWindows(Permission, TimePoint, TimePoint, Iterable)}.
* {@link DataAccessWindow#getDeviceSerialNumber() getDeviceSerialNumber()}.
* @param typeAndCompression
* for each data type to be obtained, tells the compression level; <code>0.0</code> is a good default,
* meaning "no compression". Compression is currently only supported for type {@link Type#gps_latlong} where
* the number provided represents a maximum error in degrees of latitude and longitude.
*/
Iterable<Fix> getResourceData(TimePoint startTime, TimePoint endTime, Iterable<String> serialNumbers,
Iterable<Fix> getResourceData(TimePoint startTime, TimePoint endTime, Iterable<String> deviceSerialNumbers,
Map<Type, Double> typeAndCompression) throws IllegalStateException, ClientProtocolException, IOException,
ParseException;
/**
* Shorthand for {@link #getResourceData(TimePoint, TimePoint, Iterable, Map)} where no compression is requested for
* any type.
*/
Iterable<Fix> getResourceData(TimePoint startTime, TimePoint endTime, Iterable<String> deviceSerialNumbers,
Type... types) throws IllegalStateException, ClientProtocolException, IOException,
ParseException;
/**
* Shorthand for {@link #getResourceData(TimePoint, TimePoint, Iterable, Map)} where no compression is requested for
* any type. The fixes received are forwarded to the {@link BulkFixReceiver} <code>bulkFixReceiver</code> in one call.
*/
Iterable<Fix> getAndNotifyResourceData(TimePoint startTime, TimePoint endTime,
Iterable<String> deviceSerialNumbers, BulkFixReceiver bulkFixReceiver, Type... types)
throws IllegalStateException, ClientProtocolException, IOException, ParseException;
/**
* Same as {@link #getResourceData(TimePoint, TimePoint, Iterable, Type...)}, but the resulting {@link Fix}es are
* grouped into {@link Track}s per fix type and per device.
*
* @return a map whose keys are the devices' serial numbers and whose values are the fixes produced by the device
* identified by the key, grouped in a map with the fix {@link Type} as its key. Note that if a device
* didn't produce any fixes at all under the requested parameters, its serial number may not appear as a key
* in the map. Note further that should a device not have produced fixes of a given type, that type won't
* appear as a key in the map for that device.
*/
Map<String, Map<Type, DynamicTrack<Fix>>> getResourceDataAsTracks(TimePoint startTime, TimePoint endTime,
Iterable<String> deviceSerialNumbers, Type... types) throws IllegalStateException, ClientProtocolException,
IOException, ParseException;
/**
* For the devices specified by <code>deviceSerialNumbers</code>, creates a live data connection. The <code>account</code>
* needs to be authorized to access the devices' data for the current time window. Fixes received through this connection
* are forwarded in the batches in which they are received to the listeners that can be added to the live connection
* using {@link LiveDataConnection#addListener(BulkFixReceiver)}.
*
* @return a connection that the caller can use to stop the live feed by calling {@link LiveDataConnection#disconnect()}.
*/
LiveDataConnection createLiveConnection(Iterable<String> deviceSerialNumbers) throws Exception;
/**
* @param sessionIds
* the optional IDs of the sessions for which to obtain the metadata; if <code>null</code>, all available
* sessions will be returned
* @param isPublic
* optional; if <code>null</code> or <code>true</code>, only public sessions will be included in the
* result
* @param limit
* optional; if not <code>null</code>, no more than this many session objects will be returned
* @param includeIncomplete
* optional; if not <code>false</code>, only completed sessions that have a start and an end time will be
* returned
*/
Iterable<Session> getSessions(Iterable<Long> sessionIds, Boolean isPublic, Integer limit, Boolean includeIncomplete)
throws IllegalStateException, ClientProtocolException, IOException, ParseException;
Session getSession(long id) throws IllegalStateException, ClientProtocolException, IOException, ParseException;
/**
* Returns the devices owned by the user to which the application client represented by this connection belongs.
*/
Iterable<Device> getOwnedDevices() throws IllegalStateException, ClientProtocolException, IOException, ParseException;
/**
* Returns all devices that this connection has access to with the requested <code>permission</code>. Note that
* these don't necessarily need to be devices owned by the user to which this connection belongs. The user only
* needs to have been authorized by the owner of the data to access the respective window of data.
*
* @param startTime
* optional; may be <code>null</code>. If provided, only data access windows whose time frame has a
* non-empty range after this time will be returned.
* @param endTime
* optional; may be <code>null</code>. If provided, only data access windows whose time frame has a
* non-empty range before this time will be returned.
* @param deviceSerialNumbers
* optional; if not <code>null</code> and not empty, only data access windows for the devices identified
* by these serial numbers will be returned
*/
Iterable<DataAccessWindow> getDataAccessWindows(Permission permission, TimePoint startTime, TimePoint endTime,
Iterable<String> deviceSerialNumbers) throws IllegalStateException, ClientProtocolException, IOException,
ParseException;
Iterable<Group> getGroups() throws IllegalStateException, ClientProtocolException, IOException, ParseException;
Account getAccount();
}
@@ -1,21 +1,13 @@
package com.sap.sailing.domain.igtimiadapter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.xml.sax.SAXException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
import com.sap.sailing.domain.igtimiadapter.oauth.AuthorizationCallback;
/**
@@ -26,6 +18,10 @@ import com.sap.sailing.domain.igtimiadapter.oauth.AuthorizationCallback;
* @author Axel Uhl (d043530)
*/
public interface IgtimiConnectionFactory {
Iterable<Account> getAllAccounts();
IgtimiConnection connect(Account account);
/**
* Obtains a URL that a user agent (e.g., a web browser) can be sent to in order to allow that user to authenticate
* and then authorize this factory's {@link Client} for accessing the user's Igtimi data. The URL is chosen such that
@@ -47,56 +43,17 @@ public interface IgtimiConnectionFactory {
* Matches <code>eMail</code> with the e-mail information retrieved from the "account" service earlier when an access
* token was registered.
*/
Account getAccountByEmail(String eMail);
IgtimiConnection connect(Account account);
Account getExistingAccountByEmail(String eMail);
/**
* Uses the /oauth/token service to obtain and {@link #registerAccountForWhichClientIsAuthorized(String) register}
* an access token for an authorization code which encodes the authorization given by a user to this factory's
* client.
* Tries to authorize our client on behalf of a user identified by e-mail and password. If successful, the
* account data and the relevant OAuth access token will be stored persistently.
*
* @return the account encoding the application that is authorized for a user's account
* @throws RuntimeException in case there was an error while retrieving the token
* @return the account with which a caller can then {@link #connect obtain a connection} for the data that the user
* identified by <code>userEmail</code> and <code>userPassword</code> shares with our client.
*/
Account obtainAccessTokenFromAuthorizationCode(String code) throws UnsupportedEncodingException,
ClientProtocolException, IOException, IllegalStateException, ParseException;
String getAccountUrl(Account account);
String getUsersUrl(Account account);
String getResourcesUrl(Permission permission, TimePoint startTime, TimePoint endTime,
Iterable<String> serialNumbers, Iterable<String> streamIds, Account account);
String getResourceDataUrl(TimePoint startTime, TimePoint endTime, Iterable<String> serialNumbers,
Map<Type, Double> typeAndCompression, Account account);
Iterable<URI> getWebsocketServers() throws IllegalStateException, ClientProtocolException, IOException, ParseException, URISyntaxException;
HttpClient getHttpClient();
/**
* Retrieves the JSON object to send in its string-serialized form to a web socket connection in order to receive
* live data from the units whose IDs are specified by <code>unitIds</code>. The sending units are expected to
* belong to the user account to which this factory's application client has been granted permission.
*
* @param account
* represents this factory's client's permissions to access a user account's data
* @param deviceIds
* IDs of the transmitting units expected to be visible to the account's {@link Account#getUser() user's}
*/
JSONObject getWebSocketConfigurationMessage(Account account, Iterable<String> deviceIds);
Iterable<Account> getAllAccounts();
/**
* Tries to authorize our client on behalf of a user identified by e-mail and password.
*
* @return the authorization code which can then be used to obtain a permanent access token to be used by our client
* to access data owned by the user identified by e-mail and password.
*/
String authorizeAndReturnAuthorizedCode(String userEmail, String userPassword) throws ClientProtocolException,
Account createAccountToAccessUserData(String userEmail, String userPassword) throws ClientProtocolException,
IOException, IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException,
InstantiationException, IllegalAccessException, ClassCastException;
InstantiationException, IllegalAccessException, ClassCastException, ParseException;
}
@@ -0,0 +1,43 @@
package com.sap.sailing.domain.igtimiadapter;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWA;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWS;
import com.sap.sailing.domain.igtimiadapter.datatypes.AntCbst;
import com.sap.sailing.domain.igtimiadapter.datatypes.AntHrm;
import com.sap.sailing.domain.igtimiadapter.datatypes.BatteryLevel;
import com.sap.sailing.domain.igtimiadapter.datatypes.COG;
import com.sap.sailing.domain.igtimiadapter.datatypes.File;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsAltitude;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualityHdop;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualityIndicator;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualitySatCount;
import com.sap.sailing.domain.igtimiadapter.datatypes.HDG;
import com.sap.sailing.domain.igtimiadapter.datatypes.HDGM;
import com.sap.sailing.domain.igtimiadapter.datatypes.SOG;
import com.sap.sailing.domain.igtimiadapter.datatypes.STW;
/**
* Callback interface for Igtimi fix receivers.
*
* @author Axel Uhl (d043530)
*
*/
public interface IgtimiFixReceiver {
void received(AntCbst fix);
void received(AntHrm fix);
void received(AWA fix);
void received(AWS fix);
void received(BatteryLevel fix);
void received(COG fix);
void received(File fix);
void received(GpsAltitude fix);
void received(GpsLatLong fix);
void received(GpsQualityHdop fix);
void received(GpsQualityIndicator fix);
void received(GpsQualitySatCount fix);
void received(HDG fix);
void received(HDGM fix);
void received(SOG fix);
void received(STW fix);
}
@@ -0,0 +1,85 @@
package com.sap.sailing.domain.igtimiadapter;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWA;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWS;
import com.sap.sailing.domain.igtimiadapter.datatypes.AntCbst;
import com.sap.sailing.domain.igtimiadapter.datatypes.AntHrm;
import com.sap.sailing.domain.igtimiadapter.datatypes.BatteryLevel;
import com.sap.sailing.domain.igtimiadapter.datatypes.COG;
import com.sap.sailing.domain.igtimiadapter.datatypes.File;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsAltitude;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualityHdop;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualityIndicator;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualitySatCount;
import com.sap.sailing.domain.igtimiadapter.datatypes.HDG;
import com.sap.sailing.domain.igtimiadapter.datatypes.HDGM;
import com.sap.sailing.domain.igtimiadapter.datatypes.SOG;
import com.sap.sailing.domain.igtimiadapter.datatypes.STW;
public class IgtimiFixReceiverAdapter implements IgtimiFixReceiver {
@Override
public void received(AntCbst fix) {
}
@Override
public void received(AntHrm fix) {
}
@Override
public void received(AWA fix) {
}
@Override
public void received(AWS fix) {
}
@Override
public void received(BatteryLevel fix) {
}
@Override
public void received(COG fix) {
}
@Override
public void received(File fix) {
}
@Override
public void received(GpsAltitude fix) {
}
@Override
public void received(GpsLatLong fix) {
}
@Override
public void received(GpsQualityHdop fix) {
}
@Override
public void received(GpsQualityIndicator fix) {
}
@Override
public void received(GpsQualitySatCount fix) {
}
@Override
public void received(HDG fix) {
}
@Override
public void received(HDGM fix) {
}
@Override
public void received(SOG fix) {
}
@Override
public void received(STW fix) {
}
}
@@ -0,0 +1,19 @@
package com.sap.sailing.domain.igtimiadapter;
public interface LiveDataConnection {
/**
* Disconnects this connection. Afterwards, the connection will no longer feed live data to its listener(s).
*/
void disconnect() throws Exception;
/**
* Waits for a successful connection for <code>timeoutInMillis</code> milliseconds. If the connection is successful
* within this time, <code>true</code> is returned; <code>false</code> otherwise.
*
* @param timeoutInMillis
* use 0 to wait indefinitely
*/
boolean waitForConnection(long timeoutInMillis) throws InterruptedException;
void addListener(BulkFixReceiver listener);
}
@@ -1,7 +0,0 @@
package com.sap.sailing.domain.igtimiadapter;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
public interface LiveDataListener {
void fixesReceived(Iterable<Fix> fixes);
}
@@ -1,21 +1,12 @@
package com.sap.sailing.domain.igtimiadapter;
import com.sap.sailing.domain.common.TimePoint;
public interface Resource {
public interface Resource extends HasId, HasPermissions, HasStartAndEndTime {
long getId();
boolean isBlob();
public abstract boolean isBlob();
int[] getDataTypes();
public abstract Iterable<Permission> getPermissions();
public abstract int[] getDataTypes();
public abstract String getDeviceSerialNumber();
public abstract TimePoint getEndTime();
public abstract TimePoint getStartTime();
String getDeviceSerialNumber();
}
@@ -0,0 +1,11 @@
package com.sap.sailing.domain.igtimiadapter;
/**
* Some entity to be authorized to access some data, such as a user or a group.
*
* @author Axel Uhl (d043530)
*
*/
public interface SecurityEntity extends HasId {
}
@@ -2,11 +2,16 @@ package com.sap.sailing.domain.igtimiadapter;
public interface Sensor {
/**
* The ID called "sensor ID" by Igtimi. This identifies the microcontroller-like device that can mange several sensors
* such as wind, GPS or heart rate. Together with the type code and the optional {@link #getDeviceId() device ID} as used
* The ID called "serial number" by Igtimi. This identifies the microcontroller-like device that can mange several sensors
* such as wind, GPS or heart rate. Together with the type code and the optional {@link #getSensorId() sub-device ID} as used
* for heart rate sensors the individual sensor is uniquely defined.
*/
String getTransmitterId();
String getDeviceSerialNumber();
String getDeviceId();
/**
* By default, the sensor ID is 0, particularly if it represents a sensor built into the device, such as a built-in GPS.
* In case of multiple sensors of the same kind, the non-zero ID will then help to keep the individual sensors attached
* to the same device apart. Example: multiple heart rate sensors.
*/
long getSensorId();
}
@@ -0,0 +1,37 @@
package com.sap.sailing.domain.igtimiadapter;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
/**
* Sessions are a container that gives meaning and structure to a group of {@link Device devices} that are brought
* together at a particular time for a race etc. It has a few direct properties such as {@link #getName() name},
* start+end time. Then it has the session logs which hold all the detail about what’s in the session, such as yachts,
* buoys, devices, names and colors. Sessions are shared by adding a {@link User user} into the session's {@link Group}
* identified by the {@link #getSessionGroupId() session group id}.
* <p>
*
* Sessions handle all the magic to ensure that {@link #getPermissions() permissions} to access device data (
* {@link DataAccessWindow)s} are handled when group membership changes, and as devices are added or removed to the
* session via the logs. So once you are familiar with using them it takes away all the load of managing users,
* permissions, and access to data. Without sessions, to give you access to an event's data would have meant manually
* making a list of devices used in the session and creating a bunch of {@link DataAccessWindow}s for them.
*
* @author Axel Uhl (d043530)
*
*/
public interface Session extends HasId, HasPermissions, HasStartAndEndTime {
boolean isBlob();
long getAdminSessionGroupId();
long getSessionGroupId();
long getOwnerId();
String getName();
User getOwner() throws IllegalStateException, ClientProtocolException, IOException, ParseException;
}
@@ -1,8 +1,6 @@
package com.sap.sailing.domain.igtimiadapter;
public interface User {
long getId();
public interface User extends SecurityEntity {
String getFirstName();
String getSurname();
@@ -5,15 +5,18 @@ import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* Apparent wind angle, relative to the device's or vessel's orientation
* Apparent wind angle, relative to the device's or vessel's orientation, in degrees from 0 to 360, with values 180-360
* meaning the port side, and values 0-180 meaning starboard side.
*
* @author Axel Uhl (d043530)
*
*/
public class AWA extends Fix {
private static final long serialVersionUID = -5979975741914782196L;
private final Bearing apparentWindAngle;
public AWA(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -29,4 +32,9 @@ public class AWA extends Fix {
protected String localToString() {
return "AWA: "+getApparentWindAngle();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -5,6 +5,7 @@ import java.util.Map;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
@@ -14,6 +15,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor;
*
*/
public class AWS extends Fix {
private static final long serialVersionUID = -4044229400122127582L;
private final Speed apparentWindSpeed;
public AWS(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -29,4 +31,9 @@ public class AWS extends Fix {
protected String localToString() {
return "AWS: "+getApparentWindSpeed();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
@@ -12,6 +13,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor;
*
*/
public class AntCbst extends Fix {
private static final long serialVersionUID = 7696345613317022955L;
private final int cadence;
public AntCbst(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -27,4 +29,9 @@ public class AntCbst extends Fix {
public String localToString() {
return "Cadence "+getCadence();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
@@ -12,11 +13,24 @@ import com.sap.sailing.domain.igtimiadapter.Sensor;
*
*/
public class AntHrm extends Fix {
private static final long serialVersionUID = 94260324289901500L;
private final int eventTime;
private final int heartBeatCount;
private final int heartRate;
public AntHrm(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
heartRate = ((Number) valuesPerSubindex.get(1)).intValue();
eventTime = ((Number) valuesPerSubindex.get(1)).intValue();
heartBeatCount = ((Number) valuesPerSubindex.get(2)).intValue();
heartRate = ((Number) valuesPerSubindex.get(3)).intValue();
}
public int getEventTime() {
return eventTime;
}
public int getHeartBeatCount() {
return heartBeatCount;
}
public int getHeartRate() {
@@ -27,4 +41,9 @@ public class AntHrm extends Fix {
protected String localToString() {
return "heart rate "+getHeartRate()+"/min";
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -3,9 +3,11 @@ package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class BatteryLevel extends Fix {
private static final long serialVersionUID = -8499201867081319088L;
private final double percentage;
public BatteryLevel(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -21,4 +23,9 @@ public class BatteryLevel extends Fix {
protected String localToString() {
return "Battery: "+getPercentage()+"%";
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -5,6 +5,7 @@ import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
@@ -14,6 +15,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor;
*
*/
public class COG extends Fix {
private static final long serialVersionUID = 1105936972187917331L;
private final Bearing courseOverGround;
public COG(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -29,4 +31,9 @@ public class COG extends Fix {
protected String localToString() {
return "COG: "+getCourseOverGround();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -3,9 +3,11 @@ package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class File extends Fix {
private static final long serialVersionUID = 7580174784400926003L;
private final TimePoint start;
private final TimePoint end;
private final String fileName;
@@ -51,4 +53,9 @@ public class File extends Fix {
protected String localToString() {
return "File: "+getFileName()+", MD5: "+getMd5()+" content type "+getContentType()+", "+getSize()+" bytes";
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -1,9 +1,12 @@
package com.sap.sailing.domain.igtimiadapter.datatypes;
import com.sap.sailing.domain.base.Timed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public abstract class Fix {
public abstract class Fix implements Timed {
private static final long serialVersionUID = -486253194440558670L;
private final TimePoint timePoint;
private final Sensor sensor;
@@ -12,7 +15,12 @@ public abstract class Fix {
this.timePoint = timePoint;
this.sensor = sensor;
}
public Type getType() {
return Type.getType(this.getClass());
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
@@ -23,6 +31,8 @@ public abstract class Fix {
abstract protected String localToString();
abstract public void notify(IgtimiFixReceiver receiver);
@Override
public String toString() {
return localToString() + " at "+getTimePoint()+" from "+getSensor();
@@ -5,9 +5,11 @@ import java.util.Map;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class GpsAltitude extends Fix {
private static final long serialVersionUID = -5740764665236002412L;
private final Distance altitude;
public GpsAltitude(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -23,4 +25,9 @@ public class GpsAltitude extends Fix {
protected String localToString() {
return "Altitude "+getAltitude();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -5,9 +5,11 @@ import java.util.Map;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class GpsLatLong extends Fix {
private static final long serialVersionUID = 5056284867725893553L;
private final Position position;
public GpsLatLong(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -23,4 +25,9 @@ public class GpsLatLong extends Fix {
protected String localToString() {
return position.toString();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -5,9 +5,11 @@ import java.util.Map;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class GpsQualityHdop extends Fix {
private static final long serialVersionUID = -5319766127405258823L;
private final Distance hdop;
public GpsQualityHdop(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -23,4 +25,9 @@ public class GpsQualityHdop extends Fix {
protected String localToString() {
return "HDOP: "+getHdop();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -3,9 +3,11 @@ package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class GpsQualityIndicator extends Fix {
private static final long serialVersionUID = 1112428061064399194L;
private final int quality;
public GpsQualityIndicator(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -21,4 +23,9 @@ public class GpsQualityIndicator extends Fix {
protected String localToString() {
return "GPS Quality: "+getQuality();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -3,9 +3,32 @@ package com.sap.sailing.domain.igtimiadapter.datatypes;
import java.util.Map;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
* For YachtBot and WindBot devices, this lists the number of satellites the device currently uses to determine its
* position. For the YachtBot smartphone app, restrictions may apply, as per the following e-mail by Brent Russell,
* Igtimi CTO, sent at 2013-12-01T21:16:00Z:
*
* <pre>
* You're correct, we're not reading or propagating it at the moment from mobile devices, although it works fine with YachtBot hardware.
* The mobile platform is what we're giving away for free, so we're not trying to make it a direct feature match for YachtBot :)
*
* Both mobile and YachtBot only Tx GPS coordinates when there is a valid fix, so in practice I think you should never see a zeroed
* SatQ or SatC that is of any true meaning anyway. Best just to ignore both these types (or throw away if zero) and not request it
* with the ::data call.
*
* We do have some plans to change the way fix quality information is transmitted. Mostly it's so that changes only are transmitted,
* and we'll encourage people to use hdop or gdop rather than satellite count. There's some changes we need to make under the hood
* first though, so not a near future thing.
* </pre>
*
* @author Axel Uhl (D043530)
*
*/
public class GpsQualitySatCount extends Fix {
private static final long serialVersionUID = -5507100027164944068L;
private final int satCount;
public GpsQualitySatCount(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -21,4 +44,9 @@ public class GpsQualitySatCount extends Fix {
protected String localToString() {
return ""+getSatCount()+" satellites";
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -5,6 +5,7 @@ import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
@@ -14,11 +15,12 @@ import com.sap.sailing.domain.igtimiadapter.Sensor;
*
*/
public class HDG extends Fix {
private static final long serialVersionUID = -2400420107580758551L;
private final Bearing trueHeading;
public HDG(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
trueHeading = new DegreeBearingImpl(((Number) valuesPerSubindex.get(1)).doubleValue());
trueHeading = new DegreeBearingImpl((((Number) valuesPerSubindex.get(1)).doubleValue()+360.)%360.);
}
public Bearing getTrueHeading() {
@@ -29,4 +31,9 @@ public class HDG extends Fix {
protected String localToString() {
return "HDG: "+getTrueHeading();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -5,6 +5,7 @@ import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
@@ -14,11 +15,12 @@ import com.sap.sailing.domain.igtimiadapter.Sensor;
*
*/
public class HDGM extends Fix {
private static final long serialVersionUID = -3344091983385126284L;
private final Bearing magnetigHeading;
public HDGM(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
super(sensor, timePoint);
magnetigHeading = new DegreeBearingImpl(((Number) valuesPerSubindex.get(1)).doubleValue());
magnetigHeading = new DegreeBearingImpl((((Number) valuesPerSubindex.get(1)).doubleValue()+360.)%360.);
}
public Bearing getMagnetigHeading() {
@@ -29,4 +31,9 @@ public class HDGM extends Fix {
protected String localToString() {
return "HDGM: "+getMagnetigHeading();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -5,6 +5,7 @@ import java.util.Map;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
@@ -14,6 +15,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor;
*
*/
public class SOG extends Fix {
private static final long serialVersionUID = 6926121760778539823L;
private final Speed speedOverGround;
public SOG(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -29,4 +31,9 @@ public class SOG extends Fix {
protected String localToString() {
return "SOG: "+getSpeedOverGround();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -5,6 +5,7 @@ import java.util.Map;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver;
import com.sap.sailing.domain.igtimiadapter.Sensor;
/**
@@ -14,6 +15,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor;
*
*/
public class STW extends Fix {
private static final long serialVersionUID = -7854740389524286036L;
private final Speed speedThroughWater;
public STW(TimePoint timePoint, Sensor sensor, Map<Integer, Object> valuesPerSubindex) {
@@ -29,4 +31,9 @@ public class STW extends Fix {
protected String localToString() {
return "STW: "+getSpeedThroughWater();
}
@Override
public void notify(IgtimiFixReceiver receiver) {
receiver.received(this);
}
}
@@ -4,7 +4,6 @@ import java.util.HashMap;
import java.util.Map;
public enum Type {
gps_latlong(1, GpsLatLong.class),
gps_quality_indicator(2, GpsQualityIndicator.class),
gps_quality_sat_count(3, GpsQualitySatCount.class),
@@ -37,15 +36,21 @@ public enum Type {
private final int code;
private final Class<? extends Fix> fixClass;
private final static Map<Integer, Type> typeByCode = new HashMap<>();
private final static Map<Class<? extends Fix>, Type> typeByClass = new HashMap<>();
private Type(int code, Class<? extends Fix> fixClass) {
this.code = code;
this.fixClass = fixClass;
}
public static Type getType(Class<? extends Fix> fixClass) {
return typeByClass.get(fixClass);
}
static {
for (Type type : Type.values()) {
typeByCode.put(type.getCode(), type);
typeByClass.put(type.getFixClass(), type);
}
}
}
@@ -33,7 +33,7 @@ public class Activator implements BundleActivator {
private static final String CLIENT_ID_PROPERTY_NAME = "igtimi.client.id";
private static final String CLIENT_SECRET_PROPERTY_NAME = "igtimi.client.secret";
private static final String CLIENT_REDIRECT_URI_PROPERTY_NAME = "igtimi.client.redirecturi";
private IgtimiConnectionFactory connectionFactory;
private IgtimiConnectionFactoryImpl connectionFactory;
public Activator() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
final String clientId = System.getProperty(CLIENT_ID_PROPERTY_NAME, DEFAULT_CLIENT_ID);
@@ -58,7 +58,7 @@ public class Activator implements BundleActivator {
return INSTANCE;
}
public IgtimiConnectionFactory getConnectionFactory() {
public IgtimiConnectionFactoryImpl getConnectionFactory() {
return connectionFactory;
}
@@ -0,0 +1,34 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.util.logging.Logger;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.igtimiadapter.DataAccessWindow;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.SecurityEntity;
public class DataAccessWindowDeserializer extends HasPermissionsDeserializer {
private static final Logger logger = Logger.getLogger(DataAccessWindowDeserializer.class.getName());
public DataAccessWindow createDataAccessWindowFromJson(JSONObject resourceJson, IgtimiConnection conn) {
return new DataAccessWindowImpl((Long) resourceJson.get("id"),
new MillisecondsTimePoint(((Double) resourceJson.get("start_time")).longValue()),
new MillisecondsTimePoint(((Double) resourceJson.get("end_time")).longValue()),
(String) resourceJson.get("device_serial_number"),
getPermissions((JSONObject) resourceJson.get("permissions")),
getSecurityEntity((JSONObject) resourceJson.get("recipient")));
}
private SecurityEntity getSecurityEntity(JSONObject securityEntity) {
if (securityEntity.containsKey("user")) {
return new UserDeserializer().createUserFromJson((JSONObject) securityEntity.get("user"));
} else if (securityEntity.containsKey("group")) {
return new GroupDeserializer().createGroupFromJson((JSONObject) securityEntity.get("group"));
} else {
logger.warning("Don't know how to deserialize a serucity entity from "+securityEntity);
return null;
}
}
}
@@ -0,0 +1,54 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.DataAccessWindow;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.SecurityEntity;
public class DataAccessWindowImpl extends HasIdImpl implements DataAccessWindow {
private final TimePoint startTime;
private final TimePoint endTime;
private final String deviceSerialNumber;
private final Iterable<Permission> permissions;
private final SecurityEntity recipient;
public DataAccessWindowImpl(long id, TimePoint startTime, TimePoint endTime, String deviceSerialNumber,
Iterable<Permission> permissions, SecurityEntity recipient) {
super(id);
this.startTime = startTime;
this.endTime = endTime;
this.deviceSerialNumber = deviceSerialNumber;
this.permissions = permissions;
this.recipient = recipient;
}
@Override
public TimePoint getStartTime() {
return startTime;
}
@Override
public TimePoint getEndTime() {
return endTime;
}
@Override
public String getDeviceSerialNumber() {
return deviceSerialNumber;
}
@Override
public Iterable<Permission> getPermissions() {
return permissions;
}
@Override
public SecurityEntity getRecipient() {
return recipient;
}
@Override
public String toString() {
return "DAW "+getId()+" for device "+getDeviceSerialNumber()+" from "+getStartTime()+" to "+getEndTime()+", permissions "+getPermissions();
}
}
@@ -0,0 +1,17 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.igtimiadapter.Device;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
public class DeviceDeserializer extends HasPermissionsDeserializer {
public Device createResourceFromJson(JSONObject sessionJson, IgtimiConnection conn) {
Boolean blob = (Boolean) sessionJson.get("blob");
return new DeviceImpl((Long) sessionJson.get("id"), (String) sessionJson.get("serial_number"), (String) sessionJson.get("name"),
(String) sessionJson.get("service_tag"), (Long) sessionJson.get("owner_id"),
(Long) sessionJson.get("device_user_group_id"), (Long) sessionJson.get("admin_device_user_group_id"),
getPermissions((JSONObject) sessionJson.get("permissions")),
blob == null ? false : blob, conn);
}
}
@@ -0,0 +1,88 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.igtimiadapter.Device;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.User;
public class DeviceImpl extends HasIdImpl implements Device {
private final String serialNumber;
private final String name;
private String serviceTag;
private Long ownerId;
private Long deviceUserGroupId;
private Long adminDeviceUserGroupId;
private Iterable<Permission> permissions;
private Boolean blob;
private final IgtimiConnection conn;
protected DeviceImpl(long id, String serialNumber, IgtimiConnection conn) {
this(id, serialNumber, /* name */ null, /* serviceTag */ null, /* ownerId */ null, /* deviceUserGroupId */ null,
/* adminDeviceUserGroupId */ null, /* permissions */ null, /* blob */ null, conn);
}
public DeviceImpl(Long id, String serialNumber, String name, String serviceTag, Long ownerId, Long deviceUserGroupId,
Long adminDeviceUserGroupId, Iterable<Permission> permissions, Boolean blob, IgtimiConnection conn) {
super(id);
this.serialNumber = serialNumber;
this.name = name;
this.serviceTag = serialNumber;
this.ownerId = ownerId;
this.deviceUserGroupId = deviceUserGroupId;
this.adminDeviceUserGroupId = adminDeviceUserGroupId;
this.permissions = permissions;
this.blob = blob;
this.conn = conn;
}
@Override
public String getSerialNumber() {
return serialNumber;
}
@Override
public String getServiceTag() {
return serviceTag;
}
@Override
public Long getOwnerId() {
return ownerId;
}
@Override
public Long getDeviceUserGroupId() {
return deviceUserGroupId;
}
@Override
public Long getAdminDeviceUserGroupId() {
return adminDeviceUserGroupId;
}
@Override
public User getOwner() throws IllegalStateException, ClientProtocolException, IOException, ParseException {
return conn.getUser(getOwnerId());
}
@Override
public Iterable<Permission> getPermissions() {
return permissions;
}
@Override
public Boolean getBlob() {
return blob;
}
@Override
public String getName() {
return name;
}
}
@@ -25,7 +25,7 @@ public class FixFactory {
public Iterable<Fix> createFixes(JSONObject sensorsJson) {
List<Fix> result = new ArrayList<>();
for (Entry<Object, Object> e : sensorsJson.entrySet()) {
String sensorId = (String) e.getKey();
String deviceSerialNumber = (String) e.getKey();
JSONObject typesJson = (JSONObject) e.getValue();
for (Entry<Object, Object> fixTypeAndFixesJson : typesJson.entrySet()) {
final String[] fixTypeAndOptionalColonSeparatedSensorsSubId = ((String) fixTypeAndFixesJson.getKey()).split(":");
@@ -42,7 +42,9 @@ public class FixFactory {
valuesPerSubindex.put(i, (Number) values.get(fixIndex));
i++;
}
Sensor sensor = new SensorImpl(sensorId, fixTypeAndOptionalColonSeparatedSensorsSubId.length<2?null:fixTypeAndOptionalColonSeparatedSensorsSubId[1]);
Sensor sensor = new SensorImpl(deviceSerialNumber,
fixTypeAndOptionalColonSeparatedSensorsSubId.length < 2 ? 0
: Long.valueOf(fixTypeAndOptionalColonSeparatedSensorsSubId[1]));
Fix fix = createFix(sensor, Type.valueOf(fixType), timePoint, valuesPerSubindex);
result.add(fix);
fixIndex++;
@@ -0,0 +1,18 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.igtimiadapter.Group;
public class GroupDeserializer extends HasPermissionsDeserializer {
public Group createGroupFromJson(JSONObject groupJson) {
Boolean blob = (Boolean) groupJson.get("blob");
final Long id = (Long) groupJson.get("id");
return new GroupImpl(id,
(String) groupJson.get("name"),
getPermissions((JSONObject) groupJson.get("permissions")),
(Boolean) groupJson.get("hidden"),
blob == null ? false : blob);
}
}
@@ -0,0 +1,39 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.igtimiadapter.Group;
import com.sap.sailing.domain.igtimiadapter.Permission;
public class GroupImpl extends HasIdImpl implements Group {
private final String name;
private final Iterable<Permission> permissions;
private final Boolean hidden;
private final Boolean blob;
public GroupImpl(long id, String name, Iterable<Permission> permissions, Boolean hidden, Boolean blob) {
super(id);
this.name = name;
this.permissions = permissions;
this.hidden = hidden;
this.blob = blob;
}
@Override
public String getName() {
return name;
}
@Override
public Iterable<Permission> getPermissions() {
return permissions;
}
@Override
public Boolean isHidden() {
return hidden;
}
@Override
public Boolean isBlob() {
return blob;
}
}
@@ -0,0 +1,17 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.igtimiadapter.HasId;
public class HasIdImpl implements HasId {
private final long id;
protected HasIdImpl(long id) {
super();
this.id = id;
}
@Override
public long getId() {
return id;
}
}
@@ -0,0 +1,26 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.igtimiadapter.Permission;
public abstract class HasPermissionsDeserializer {
protected Iterable<Permission> getPermissions(JSONObject permissions) {
final List<Permission> result;
if (permissions == null) {
result = null;
} else {
result = new ArrayList<>();
for (Entry<Object, Object> e : permissions.entrySet()) {
if ((Boolean) e.getValue()) {
result.add(Permission.valueOf((String) e.getKey()));
}
}
}
return result;
}
}
@@ -120,7 +120,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
}
@Override
public Account getAccountByEmail(String eMail) {
public Account getExistingAccountByEmail(String eMail) {
return accountsByEmail.get(eMail);
}
@@ -138,12 +138,20 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
return accessTokensByAccount.get(account);
}
@Override
public String getAccountUrl(Account account) {
return getApiV1BaseUrl()+"account?"+getAccessTokenUrlParameter(account);
}
@Override
/**
* Retrieves the JSON object to send in its string-serialized form to a web socket connection in order to receive
* live data from the units whose IDs are specified by <code>unitIds</code>. The sending units are expected to
* belong to the user account to which this factory's application client has been granted permission.
*
* @param account
* represents this factory's client's permissions to access a user account's data
* @param deviceIds
* IDs of the transmitting units expected to be visible to the account's {@link Account#getUser() user's}
*/
public JSONObject getWebSocketConfigurationMessage(Account account, Iterable<String> deviceIds) {
JSONObject result = new JSONObject();
result.put("access_token", getAccessTokenForAccount(account));
@@ -155,12 +163,54 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
return result;
}
@Override
public String getUsersUrl(Account account) {
return getApiV1BaseUrl()+"users?"+getAccessTokenUrlParameter(account);
}
@Override
public String getUserUrl(long id, Account account) {
return getApiV1BaseUrl()+"users/"+id+"?"+getAccessTokenUrlParameter(account);
}
public String getGroupsUrl(Account account) {
return getApiV1BaseUrl()+"groups?"+getAccessTokenUrlParameter(account);
}
public String getGroupUrl(long id, Account account) {
return getApiV1BaseUrl()+"groups/"+id+"?"+getAccessTokenUrlParameter(account);
}
public String getSessionUrl(long id, Account account) {
return getApiV1BaseUrl()+"sessions/"+id+"?"+getAccessTokenUrlParameter(account);
}
public String getOwnedDevicesUrl(Account account) {
return getApiV1BaseUrl()+"devices/?"+getAccessTokenUrlParameter(account);
}
public String getDataAccessWindowsUrl(Permission permission, TimePoint startTime, TimePoint endTime,
Iterable<String> deviceSerialNumbers, Account account) {
StringBuilder url = new StringBuilder(getApiV1BaseUrl());
url.append("devices/data_access_windows?type=");
url.append(permission.name());
if (startTime != null) {
url.append("&start_time=");
url.append(startTime.asMillis());
}
if (endTime != null) {
url.append("&end_time=");
url.append(endTime.asMillis());
}
if (deviceSerialNumbers != null) {
for (String serialNumber : deviceSerialNumbers) {
url.append("&serial_numbers[]=");
url.append(serialNumber);
}
}
url.append("&");
url.append(getAccessTokenUrlParameter(account));
return url.toString();
}
public String getResourceDataUrl(TimePoint startTime, TimePoint endTime, Iterable<String> serialNumbers,
Map<Type, Double> typeAndCompression, Account account) {
StringBuilder url = new StringBuilder(getApiV1BaseUrl());
@@ -180,7 +230,6 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
return url.toString();
}
@Override
public String getResourcesUrl(Permission permission, TimePoint startTime, TimePoint endTime,
Iterable<String> serialNumbers, Iterable<String> streamIds, Account account) {
StringBuilder url = new StringBuilder(getApiV1BaseUrl());
@@ -212,6 +261,32 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
return url.toString();
}
public String getSessionsUrl(Iterable<Long> sessionIds, Boolean isPublic, Integer limit, Boolean includeIncomplete, Account account) {
StringBuilder url = new StringBuilder(getApiV1BaseUrl());
url.append("sessions?");
url.append(getAccessTokenUrlParameter(account));
if (sessionIds != null) {
for (Long sessionId : sessionIds) {
url.append("&ids[]=");
url.append(sessionId);
}
}
if (isPublic != null) {
url.append("&public=");
url.append(isPublic.toString().toLowerCase());
}
if (limit != null) {
url.append("&limit=");
url.append(limit);
}
if (includeIncomplete != null) {
url.append("&include_incomplete");
url.append(includeIncomplete.toString().toLowerCase());
}
return url.toString();
}
private String getAccessTokenUrlParameter(Account account) {
return "access_token="+getAccessTokenForAccount(account);
}
@@ -225,7 +300,14 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
return client;
}
@Override
/**
* Uses the /oauth/token service to obtain and {@link #registerAccountForWhichClientIsAuthorized(String) register}
* an access token for an authorization code which encodes the authorization given by a user to this factory's
* client.
*
* @return the account encoding the application that is authorized for a user's account
* @throws RuntimeException in case there was an error while retrieving the token
*/
public Account obtainAccessTokenFromAuthorizationCode(String code) throws ClientProtocolException, IOException, IllegalStateException, ParseException {
HttpClient client = new SystemDefaultHttpClient();
HttpPost post = new HttpPost(getOauthTokenUrl());
@@ -248,7 +330,12 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
return result;
}
@Override
/**
* Tries to authorize our client on behalf of a user identified by e-mail and password.
*
* @return the authorization code which can then be used to obtain a permanent access token to be used by our client
* to access data owned by the user identified by e-mail and password.
*/
public String authorizeAndReturnAuthorizedCode(String userEmail, String userPassword)
throws ClientProtocolException, IOException, IllegalStateException, ParserConfigurationException,
SAXException, ClassNotFoundException, InstantiationException, IllegalAccessException, ClassCastException {
@@ -261,6 +348,20 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
HttpResponse responseForAuthorize = client.execute(get);
return signInAndReturnAuthorizationForm(client, responseForAuthorize, userEmail, userPassword);
}
@Override
public Account createAccountToAccessUserData(String userEmail, String userPassword) throws ClientProtocolException,
IOException, IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException,
InstantiationException, IllegalAccessException, ClassCastException, ParseException {
final Account result;
String code = authorizeAndReturnAuthorizedCode(userEmail, userPassword);
if (code != null) {
result = obtainAccessTokenFromAuthorizationCode(code);
} else {
result = null;
}
return result;
}
private String signInAndReturnAuthorizationForm(DefaultHttpClient client, HttpResponse response,
final String userEmail, final String userPassword) throws ParserConfigurationException, SAXException,
@@ -314,10 +415,6 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
* Parses the form in the <code>autorizationForm</code> response and posts it by submitting the form that contains
* the commit button with the value "Authorize". If a redirect strategy is set on the <code>client</code> it will
* see the redirect URL in the <code>Location</code> header. The redirection target is closed immediately.
* @throws ClassCastException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws ClassNotFoundException
*/
private void authorizeAndGetCode(HttpResponse authorizationForm, DefaultHttpClient client)
throws IllegalStateException, SAXException, IOException, ParserConfigurationException,
@@ -364,13 +461,11 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
authorizationResponse.getEntity().getContent().close();
}
@Override
public HttpClient getHttpClient() {
HttpClient client = new SystemDefaultHttpClient();
return client;
}
@Override
public Iterable<URI> getWebsocketServers() throws IllegalStateException, ClientProtocolException, IOException, ParseException, URISyntaxException {
HttpClient client = getHttpClient();
HttpGet getWebsocketServers = new HttpGet("https://www.igtimi.com/server_listers/web_sockets");
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.igtimiadapter.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -14,23 +15,36 @@ import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver;
import com.sap.sailing.domain.igtimiadapter.DataAccessWindow;
import com.sap.sailing.domain.igtimiadapter.Device;
import com.sap.sailing.domain.igtimiadapter.Group;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.LiveDataConnection;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.Resource;
import com.sap.sailing.domain.igtimiadapter.Session;
import com.sap.sailing.domain.igtimiadapter.User;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
import com.sap.sailing.domain.igtimiadapter.websocket.WebSocketConnectionManager;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.impl.DynamicTrackImpl;
public class IgtimiConnectionImpl implements IgtimiConnection {
private final Account account;
private final IgtimiConnectionFactory connectionFactory;
private final IgtimiConnectionFactoryImpl connectionFactory;
public IgtimiConnectionImpl(IgtimiConnectionFactory connectionFactory, Account account) {
public IgtimiConnectionImpl(IgtimiConnectionFactoryImpl connectionFactory, Account account) {
this.connectionFactory = connectionFactory;
this.account = account;
}
@Override
public Account getAccount() {
return account;
}
@Override
public Iterable<User> getUsers() throws IllegalStateException, ClientProtocolException, IOException, ParseException {
HttpClient client = connectionFactory.getHttpClient();
@@ -44,6 +58,28 @@ public class IgtimiConnectionImpl implements IgtimiConnection {
return result;
}
@Override
public User getUser(long id) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
HttpClient client = connectionFactory.getHttpClient();
HttpGet getUsers = new HttpGet(connectionFactory.getUserUrl(id, account));
JSONObject userJson = ConnectivityUtils.getJsonFromResponse(client.execute(getUsers));
User result = new UserDeserializer().createUserFromJson((JSONObject) ((JSONObject) userJson).get("user"));
return result;
}
@Override
public Iterable<Group> getGroups() throws IllegalStateException, ClientProtocolException, IOException, ParseException {
HttpClient client = connectionFactory.getHttpClient();
HttpGet getGroups = new HttpGet(connectionFactory.getGroupsUrl(account));
JSONObject groupsJson = ConnectivityUtils.getJsonFromResponse(client.execute(getGroups));
final List<Group> result = new ArrayList<>();
for (Object groupJson : (JSONArray) groupsJson.get("groups")) {
Group group = new GroupDeserializer().createGroupFromJson((JSONObject) ((JSONObject) groupJson).get("group"));
result.add(group);
}
return result;
}
@Override
public Iterable<Resource> getResources(Permission permission, TimePoint startTime, TimePoint endTime,
Iterable<String> deviceIds, Iterable<String> streamIds) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
@@ -51,19 +87,126 @@ public class IgtimiConnectionImpl implements IgtimiConnection {
HttpGet getResources = new HttpGet(connectionFactory.getResourcesUrl(permission, startTime, endTime, deviceIds, streamIds, account));
JSONObject resourcesJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResources));
final List<Resource> result = new ArrayList<>();
for (Object userJson : (JSONArray) resourcesJson.get("resources")) {
Resource user = new ResourceDeserializer().createResourceFromJson((JSONObject) ((JSONObject) userJson).get("resource"));
result.add(user);
for (Object resourceJson : (JSONArray) resourcesJson.get("resources")) {
Resource resource = new ResourceDeserializer().createResourceFromJson((JSONObject) ((JSONObject) resourceJson).get("resource"), this);
result.add(resource);
}
return result;
}
@Override
public Iterable<Session> getSessions(Iterable<Long> sessionIds, Boolean isPublic, Integer limit,
Boolean includeIncomplete) throws IllegalStateException, ClientProtocolException, IOException,
ParseException {
HttpClient client = connectionFactory.getHttpClient();
HttpGet getResources = new HttpGet(connectionFactory.getSessionsUrl(sessionIds, isPublic, limit, includeIncomplete, account));
JSONObject sessionsJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResources));
final List<Session> result = new ArrayList<>();
for (Object sessionJson : (JSONArray) sessionsJson.get("sessions")) {
Session session = new SessionDeserializer().createResourceFromJson((JSONObject) ((JSONObject) sessionJson).get("session"), this);
result.add(session);
}
return result;
}
@Override
public Iterable<Fix> getResourceData(TimePoint startTime, TimePoint endTime,
Iterable<String> serialNumbers, Map<Type, Double> typeAndCompression) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
public Session getSession(long id) throws IllegalStateException, ClientProtocolException, IOException,
ParseException {
HttpClient client = connectionFactory.getHttpClient();
HttpGet getResourceData = new HttpGet(connectionFactory.getResourceDataUrl(startTime, endTime, serialNumbers, typeAndCompression, account));
HttpGet getSessions = new HttpGet(connectionFactory.getSessionUrl(id, account));
JSONObject sessionJson = ConnectivityUtils.getJsonFromResponse(client.execute(getSessions));
Session result = new SessionDeserializer().createResourceFromJson((JSONObject) ((JSONObject) sessionJson).get("session"), this);
return result;
}
@Override
public Iterable<Fix> getResourceData(TimePoint startTime, TimePoint endTime,
Iterable<String> deviceSerialNumbers, Map<Type, Double> typeAndCompression) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
HttpClient client = connectionFactory.getHttpClient();
HttpGet getResourceData = new HttpGet(connectionFactory.getResourceDataUrl(startTime, endTime, deviceSerialNumbers, typeAndCompression, account));
JSONObject resourceDataJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResourceData));
return new FixFactory().createFixes(resourceDataJson);
}
@Override
public Iterable<Fix> getResourceData(TimePoint startTime, TimePoint endTime, Iterable<String> deviceSerialNumbers,
Type... types) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
Map<Type, Double> typeAndCompression = new HashMap<>();
for (Type type : types) {
typeAndCompression.put(type, 0.0);
}
return getResourceData(startTime, endTime, deviceSerialNumbers, typeAndCompression);
}
@Override
public Iterable<Fix> getAndNotifyResourceData(TimePoint startTime, TimePoint endTime,
Iterable<String> deviceSerialNumbers, BulkFixReceiver bulkFixReceiver, Type... types)
throws IllegalStateException, ClientProtocolException, IOException, ParseException {
Iterable<Fix> result = getResourceData(startTime, endTime, deviceSerialNumbers, types);
bulkFixReceiver.received(result);
return result;
}
@Override
public Map<String, Map<Type, DynamicTrack<Fix>>> getResourceDataAsTracks(TimePoint startTime, TimePoint endTime, Iterable<String> deviceSerialNumbers,
Type... types) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
Iterable<Fix> fixes = getResourceData(startTime, endTime, deviceSerialNumbers, types);
Map<String, Map<Type, DynamicTrack<Fix>>> result = new HashMap<>();
for (Fix fix : fixes) {
String deviceSerialNumber = fix.getSensor().getDeviceSerialNumber();
Type type = fix.getType();
DynamicTrack<Fix> track = (DynamicTrack<Fix>) getOrCreateTrack(result, deviceSerialNumber, type);
track.add(fix);
}
return result;
}
@Override
public LiveDataConnection createLiveConnection(Iterable<String> deviceSerialNumbers) throws Exception {
return new WebSocketConnectionManager(connectionFactory, deviceSerialNumbers, getAccount());
}
private DynamicTrack<Fix> getOrCreateTrack(Map<String, Map<Type, DynamicTrack<Fix>>> result,
String deviceSerialNumber, Type type) {
Map<Type, DynamicTrack<Fix>> mapForDevice = result.get(deviceSerialNumber);
if (mapForDevice == null) {
mapForDevice = new HashMap<>();
result.put(deviceSerialNumber, mapForDevice);
}
DynamicTrack<Fix> track = mapForDevice.get(type);
if (track == null) {
track = new DynamicTrackImpl<Fix>("Track for Igtimi fixes of type "+type.name()+" for "+this);
mapForDevice.put(type, track);
}
return track;
}
@Override
public Iterable<Device> getOwnedDevices() throws IllegalStateException, ClientProtocolException, IOException, ParseException {
HttpClient client = connectionFactory.getHttpClient();
HttpGet getResources = new HttpGet(connectionFactory.getOwnedDevicesUrl(account));
JSONObject devicesJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResources));
final List<Device> result = new ArrayList<>();
for (Object deviceJson : (JSONArray) devicesJson.get("devices")) {
Device device = new DeviceDeserializer().createResourceFromJson((JSONObject) ((JSONObject) deviceJson).get("device"), this);
result.add(device);
}
return result;
}
@Override
public Iterable<DataAccessWindow> getDataAccessWindows(Permission permission, TimePoint startTime,
TimePoint endTime, Iterable<String> deviceSerialNumbers) throws IllegalStateException, ClientProtocolException, IOException, ParseException {
HttpClient client = connectionFactory.getHttpClient();
HttpGet getDataAccessWindows = new HttpGet(connectionFactory.getDataAccessWindowsUrl(permission, startTime, endTime, deviceSerialNumbers, account));
JSONObject dataAccessWindowsJson = ConnectivityUtils.getJsonFromResponse(client.execute(getDataAccessWindows));
final List<DataAccessWindow> result = new ArrayList<>();
for (Object dataAccessWindowJson : (JSONArray) dataAccessWindowsJson.get("data_access_windows")) {
DataAccessWindow dataAccessWindow = new DataAccessWindowDeserializer().createDataAccessWindowFromJson(
(JSONObject) ((JSONObject) dataAccessWindowJson).get("data_access_window"), this);
result.add(dataAccessWindow);
}
return result;
}
}
@@ -0,0 +1,402 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.declination.DeclinationService;
import com.sap.sailing.domain.base.Timed;
import com.sap.sailing.domain.base.impl.ScalableBearing;
import com.sap.sailing.domain.base.impl.ScalablePosition;
import com.sap.sailing.domain.base.impl.ScalableSpeed;
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.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.confidence.ScalableValue;
import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver;
import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiverAdapter;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWA;
import com.sap.sailing.domain.igtimiadapter.datatypes.AWS;
import com.sap.sailing.domain.igtimiadapter.datatypes.COG;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong;
import com.sap.sailing.domain.igtimiadapter.datatypes.HDG;
import com.sap.sailing.domain.igtimiadapter.datatypes.HDGM;
import com.sap.sailing.domain.igtimiadapter.datatypes.SOG;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindListener;
import com.sap.sailing.domain.tracking.impl.DynamicTrackImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
/**
* Receives Igtimi {@link Fix}es and tries to generate a {@link Wind} object from each {@link AWS} fix. For this to
* work, the time-wise adjacent {@link AWA}, {@link HDG}/{@link HDGM} and {@link GpsLatLong} fixes are used. If only
* a magnetic heading ({@link HDGM}) is available, the {@link DeclinationService} is used to map that to a true heading.
* The true wind direction is determined by adding the boat speed vector onto the apparent wind vector.
*
* @author Axel Uhl (d043530)
*
*/
public class IgtimiWindReceiver implements BulkFixReceiver {
private static final Logger logger = Logger.getLogger(IgtimiWindReceiver.class.getName());
private final DynamicTrack<AWA> awaTrack;
private final DynamicTrack<AWS> awsTrack;
private final DynamicTrack<GpsLatLong> gpsTrack;
private final DynamicTrack<COG> cogTrack;
private final DynamicTrack<SOG> sogTrack;
private final DynamicTrack<HDG> hdgTrack;
private final DynamicTrack<HDGM> hdgmTrack;
private final FixReceiver receiver;
private final DeclinationService declinationService;
private final ConcurrentHashMap<WindListener, WindListener> listeners;
private class FixReceiver extends IgtimiFixReceiverAdapter {
@Override
public void received(AWA fix) {
getAwaTrack().add(fix);
}
@Override
public void received(AWS fix) {
getAwsTrack().add(fix);
}
@Override
public void received(GpsLatLong fix) {
getGpsTrack().add(fix);
}
@Override
public void received(COG fix) {
getCogTrack().add(fix);
}
@Override
public void received(SOG fix) {
getSogTrack().add(fix);
}
@Override
public void received(HDG fix) {
getHdgTrack().add(fix);
}
@Override
public void received(HDGM fix) {
getHdgmTrack().add(fix);
}
}
public IgtimiWindReceiver(String deviceSerialNumber) {
receiver = new FixReceiver();
declinationService = DeclinationService.INSTANCE;
listeners = new ConcurrentHashMap<>();
awaTrack = new DynamicTrackImpl<>("AWA Track for Igtimi wind track for device "+deviceSerialNumber);
awsTrack = new DynamicTrackImpl<>("AWS Track for Igtimi wind track for device "+deviceSerialNumber);
gpsTrack = new DynamicTrackImpl<>("GPS Track for Igtimi wind track for device "+deviceSerialNumber);
cogTrack = new DynamicTrackImpl<>("COG Track for Igtimi wind track for device "+deviceSerialNumber);
sogTrack = new DynamicTrackImpl<>("SOG Track for Igtimi wind track for device "+deviceSerialNumber);
hdgTrack = new DynamicTrackImpl<>("HDG Track for Igtimi wind track for device "+deviceSerialNumber);
hdgmTrack = new DynamicTrackImpl<>("HDGM Track for Igtimi wind track for device "+deviceSerialNumber);
}
/**
* Called when a batch of fixes was received, e.g., after loading from stored data or from a live web socket receiver.
* The fixes are pumped into the tracks by the superclass implementation. Then, for all new {@link AWS} fixes, a
* call to {@link IgtimiWindReceiver#getWind} is performed.
*/
@Override
public void received(Iterable<Fix> fixes) {
final List<AWS> awsFixes = new ArrayList<>();
for (Fix fix : fixes) {
fix.notify(receiver);
fix.notify(new IgtimiFixReceiverAdapter() {
@Override
public void received(AWS fix) {
awsFixes.add(fix);
}
});
}
for (AWS aws : awsFixes) {
try {
final Wind wind = getWind(aws.getTimePoint());
if (wind != null) {
notifyListeners(wind);
}
} catch (ClassNotFoundException | IOException | ParseException e) {
logger.log(Level.INFO, "Exception while trying to construct Wind fix from Igtimi fix " + aws, e);
}
}
}
public void addListener(WindListener listener) {
listeners.put(listener, listener);
}
public void notifyListeners(Wind wind) {
for (WindListener listener : listeners.keySet()) {
listener.windDataReceived(wind);
}
}
private Wind getWind(TimePoint timePoint) throws ClassNotFoundException, IOException, ParseException {
final Wind result;
Pair<AWA, AWA> awaPair = getSurroundingFixes(getAwaTrack(), timePoint);
Bearing awa = getAWA(timePoint, awaPair);
Pair<AWS, AWS> awsPair = getSurroundingFixes(getAwsTrack(), timePoint);
Speed aws = getAWS(timePoint, awsPair);
Pair<GpsLatLong, GpsLatLong> gpsPair = getSurroundingFixes(getGpsTrack(), timePoint);
Position pos = getPosition(timePoint, gpsPair);
Pair<HDG, HDG> hdgPair = getSurroundingFixes(getHdgTrack(), timePoint);
Pair<HDGM, HDGM> hdgmPair = getSurroundingFixes(getHdgmTrack(), timePoint);
Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos);
if (awa != null && aws != null && pos != null && heading != null) {
Bearing apparentWindDirection = heading.add(awa);
SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection);
/*
* Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to improve
* precision of boat speed / coarse over SOG/COG measurements:
*
* "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of
* smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter might
* happen as a satellite is dropped/acquired - and I'd expect to see a time correlated position jump as
* well. Probably not though a direction/speed correlation :)
*
* All our GPS systems are using Doppler to calculate COG/SOG and this should be the most accurate measure.
* I don't believe that delta position really adds any more "truth" to the measurement of physical reality,
* if that makes sense. I'd trust d-p even less at low speeds, where you'll see the most disagreement. Also
* there is a significant quantisation noise error in the d-p calculations from the GPS resolution too, so
* you'd have to smooth it before averaging - possibly in a speed dependent way.
*
* Remember that Doppler COG/SOG is using the same raw satellite measurements that are being used to
* calculate position, just the algorithm is different. I suspect that merging the two might be, in
* practice, just a slightly indirect way of averaging. If you like the central limit theorem in action over
* the set of algorithms!
*
* So again, my personal preference would be to work with the data that should be the most accurate
* (COG/SOG) and consider algorithms that handle smoothing of that data best."
*/
Pair<SOG, SOG> sogPair = getSurroundingFixes(getSogTrack(), timePoint);
Speed sog = getSOG(timePoint, sogPair);
Pair<COG, COG> cogPair = getSurroundingFixes(getCogTrack(), timePoint);
Bearing cog = getCOG(timePoint, cogPair);
SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog);
SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog);
result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection);
} else {
result = null;
}
return result;
}
private Bearing getAWA(TimePoint timePoint, Pair<AWA, AWA> awaPair) {
final Bearing awaFrom;
if (awaPair.getA() == null) {
if (awaPair.getB() == null) {
awaFrom = null;
} else {
awaFrom = awaPair.getB().getApparentWindAngle();
}
} else {
if (awaPair.getB() == null) {
awaFrom = awaPair.getA().getApparentWindAngle();
} else {
awaFrom = timeBasedAgerage(timePoint,
new ScalableBearing(awaPair.getA().getApparentWindAngle()), awaPair.getA().getTimePoint(),
new ScalableBearing(awaPair.getB().getApparentWindAngle()), awaPair.getB().getTimePoint());
}
}
return awaFrom == null ? null : awaFrom.reverse();
}
private Speed getAWS(TimePoint timePoint, Pair<AWS, AWS> awsPair) {
final Speed aws;
if (awsPair.getA() == null) {
if (awsPair.getB() == null) {
aws = null;
} else {
aws = awsPair.getB().getApparentWindSpeed();
}
} else {
if (awsPair.getB() == null) {
aws = awsPair.getA().getApparentWindSpeed();
} else {
aws = timeBasedAgerage(timePoint,
new ScalableSpeed(awsPair.getA().getApparentWindSpeed()), awsPair.getA().getTimePoint(),
new ScalableSpeed(awsPair.getB().getApparentWindSpeed()), awsPair.getB().getTimePoint());
}
}
return aws;
}
private Speed getSOG(TimePoint timePoint, Pair<SOG, SOG> sogPair) {
final Speed sog;
if (sogPair.getA() == null) {
if (sogPair.getB() == null) {
sog = null;
} else {
sog = sogPair.getB().getSpeedOverGround();
}
} else {
if (sogPair.getB() == null) {
sog = sogPair.getA().getSpeedOverGround();
} else {
sog = timeBasedAgerage(timePoint,
new ScalableSpeed(sogPair.getA().getSpeedOverGround()), sogPair.getA().getTimePoint(),
new ScalableSpeed(sogPair.getB().getSpeedOverGround()), sogPair.getB().getTimePoint());
}
}
return sog;
}
private Bearing getCOG(TimePoint timePoint, Pair<COG, COG> cogPair) {
final Bearing sog;
if (cogPair.getA() == null) {
if (cogPair.getB() == null) {
sog = null;
} else {
sog = cogPair.getB().getCourseOverGround();
}
} else {
if (cogPair.getB() == null) {
sog = cogPair.getA().getCourseOverGround();
} else {
sog = timeBasedAgerage(timePoint,
new ScalableBearing(cogPair.getA().getCourseOverGround()), cogPair.getA().getTimePoint(),
new ScalableBearing(cogPair.getB().getCourseOverGround()), cogPair.getB().getTimePoint());
}
}
return sog;
}
private Position getPosition(TimePoint timePoint, Pair<GpsLatLong, GpsLatLong> gpsPair) {
final Position pos;
if (gpsPair.getA() == null) {
if (gpsPair.getB() == null) {
pos = null;
} else {
pos = gpsPair.getB().getPosition();
}
} else {
if (gpsPair.getB() == null) {
pos = gpsPair.getA().getPosition();
} else {
pos = timeBasedAgerage(timePoint, new ScalablePosition(gpsPair.getA().getPosition()), gpsPair.getA()
.getTimePoint(), new ScalablePosition(gpsPair.getB().getPosition()), gpsPair.getB()
.getTimePoint());
}
}
return pos;
}
private Bearing getHDG(TimePoint timePoint, Pair<HDG, HDG> hdgPair) {
final Bearing hdg;
if (hdgPair.getA() == null) {
if (hdgPair.getB() == null) {
hdg = null;
} else {
hdg = hdgPair.getB().getTrueHeading();
}
} else {
if (hdgPair.getB() == null) {
hdg = hdgPair.getA().getTrueHeading();
} else {
hdg = timeBasedAgerage(timePoint,
new ScalableBearing(hdgPair.getA().getTrueHeading()), hdgPair.getA().getTimePoint(),
new ScalableBearing(hdgPair.getB().getTrueHeading()), hdgPair.getB().getTimePoint());
}
}
return hdg;
}
private Bearing getHDGM(TimePoint timePoint, Pair<HDGM, HDGM> hdgmPair) {
final Bearing hdgm;
if (hdgmPair.getA() == null) {
if (hdgmPair.getB() == null) {
hdgm = null;
} else {
hdgm = hdgmPair.getB().getMagnetigHeading();
}
} else {
if (hdgmPair.getB() == null) {
hdgm = hdgmPair.getA().getMagnetigHeading();
} else {
hdgm = timeBasedAgerage(timePoint,
new ScalableBearing(hdgmPair.getA().getMagnetigHeading()), hdgmPair.getA().getTimePoint(),
new ScalableBearing(hdgmPair.getB().getMagnetigHeading()), hdgmPair.getB().getTimePoint());
}
}
return hdgm;
}
private Bearing getHeading(TimePoint timePoint, Pair<HDG, HDG> hdgPair, Pair<HDGM, HDGM> hdgmPair, Position position) throws ClassNotFoundException, IOException, ParseException {
final Bearing trueHeading;
Bearing hdg = getHDG(timePoint, hdgPair);
if (hdg != null) {
trueHeading = hdg;
} else {
Bearing hdgm = getHDGM(timePoint, hdgmPair);
Declination declination = declinationService.getDeclination(timePoint, position, /* timeoutForOnlineFetchInMilliseconds 5s */ 5000);
trueHeading = hdgm.add(declination.getBearingCorrectedTo(timePoint));
}
return trueHeading;
}
private <V, T> T timeBasedAgerage(TimePoint timePoint, ScalableValue<V, T> value1, TimePoint timePoint1, ScalableValue<V, T> value2, TimePoint timePoint2) {
final T acc;
if (timePoint1.equals(timePoint2)) {
acc = value1.add(value2).divide(2);
} else {
long timeDiff1 = Math.abs(timePoint1.asMillis() - timePoint.asMillis());
long timeDiff2 = Math.abs(timePoint2.asMillis() - timePoint.asMillis());
acc = value1.multiply(timeDiff2).add(value2.multiply(timeDiff1)).divide(timeDiff1 + timeDiff2);
}
return acc;
}
private <T extends Timed> Pair<T, T> getSurroundingFixes(Track<T> track, TimePoint timePoint) {
T left = track.getLastFixAtOrBefore(timePoint);
T right = track.getFirstFixAtOrAfter(timePoint);
Pair<T, T> result = new Pair<>(left, right);
return result;
}
private DynamicTrack<AWA> getAwaTrack() {
return awaTrack;
}
private DynamicTrack<AWS> getAwsTrack() {
return awsTrack;
}
private DynamicTrack<GpsLatLong> getGpsTrack() {
return gpsTrack;
}
private DynamicTrack<COG> getCogTrack() {
return cogTrack;
}
private DynamicTrack<SOG> getSogTrack() {
return sogTrack;
}
private DynamicTrack<HDG> getHdgTrack() {
return hdgTrack;
}
private DynamicTrack<HDGM> getHdgmTrack() {
return hdgmTrack;
}
}
@@ -1,18 +1,14 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.Resource;
public class ResourceDeserializer {
public Resource createResourceFromJson(JSONObject resourceJson) {
public class ResourceDeserializer extends HasPermissionsDeserializer {
public Resource createResourceFromJson(JSONObject resourceJson, IgtimiConnection conn) {
Boolean blob = (Boolean) resourceJson.get("blob");
return new ResourceImpl((Long) resourceJson.get("id"),
new MillisecondsTimePoint(((Double) resourceJson.get("start_time")).longValue()),
@@ -20,17 +16,7 @@ public class ResourceDeserializer {
(String) resourceJson.get("device_serial_number"),
getDataTypes((JSONArray) resourceJson.get("data_types")),
getPermissions((JSONObject) resourceJson.get("permissions")),
blob == null ? false : blob);
}
private Iterable<Permission> getPermissions(JSONObject permissions) {
final List<Permission> result = new ArrayList<>();
for (Entry<Object, Object> e : permissions.entrySet()) {
if ((Boolean) e.getValue()) {
result.add(Permission.valueOf((String) e.getKey()));
}
}
return result;
blob == null ? false : blob, conn);
}
private int[] getDataTypes(JSONArray jsonArray) {
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.Resource;
@@ -14,7 +15,7 @@ public class ResourceImpl implements Resource {
private final boolean blob;
public ResourceImpl(long id, TimePoint startTime, TimePoint endTime, String deviceSerialNumber, int[] dataTypes,
Iterable<Permission> permissions, boolean blob) {
Iterable<Permission> permissions, boolean blob, IgtimiConnection conn) {
super();
this.id = id;
this.startTime = startTime;
@@ -3,35 +3,36 @@ package com.sap.sailing.domain.igtimiadapter.impl;
import com.sap.sailing.domain.igtimiadapter.Sensor;
public class SensorImpl implements Sensor {
private final String transmitterId;
private final String deviceId;
protected SensorImpl(String transmitterId, String deviceId) {
private final String deviceSerialNumber;
private final long subDeviceId;
protected SensorImpl(String deviceSerialNumber, long subDeviceId) {
super();
this.transmitterId = transmitterId;
this.deviceId = deviceId;
this.deviceSerialNumber = deviceSerialNumber;
this.subDeviceId = subDeviceId;
}
@Override
public String getTransmitterId() {
return transmitterId;
public String getDeviceSerialNumber() {
return deviceSerialNumber;
}
@Override
public String getDeviceId() {
return deviceId;
public long getSensorId() {
return subDeviceId;
}
@Override
public String toString() {
return "Transmitter "+getTransmitterId()+(getDeviceId()==null?"":(", device "+getDeviceId()));
return "Device "+getDeviceSerialNumber()+", sensor "+getSensorId();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((deviceId == null) ? 0 : deviceId.hashCode());
result = prime * result + ((transmitterId == null) ? 0 : transmitterId.hashCode());
result = prime * result + ((deviceSerialNumber == null) ? 0 : deviceSerialNumber.hashCode());
result = prime * result + (int) (subDeviceId ^ (subDeviceId >>> 32));
return result;
}
@@ -44,15 +45,12 @@ public class SensorImpl implements Sensor {
if (getClass() != obj.getClass())
return false;
SensorImpl other = (SensorImpl) obj;
if (deviceId == null) {
if (other.deviceId != null)
if (deviceSerialNumber == null) {
if (other.deviceSerialNumber != null)
return false;
} else if (!deviceId.equals(other.deviceId))
} else if (!deviceSerialNumber.equals(other.deviceSerialNumber))
return false;
if (transmitterId == null) {
if (other.transmitterId != null)
return false;
} else if (!transmitterId.equals(other.transmitterId))
if (subDeviceId != other.subDeviceId)
return false;
return true;
}
@@ -0,0 +1,21 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.Session;
public class SessionDeserializer extends HasPermissionsDeserializer {
public Session createResourceFromJson(JSONObject sessionJson, IgtimiConnection conn) {
Boolean blob = (Boolean) sessionJson.get("blob");
final Double startTime = (Double) sessionJson.get("start_time");
final Double endTime = (Double) sessionJson.get("end_time");
return new SessionImpl((Long) sessionJson.get("id"), (String) sessionJson.get("name"), (Long) sessionJson.get("owner_id"),
(Long) sessionJson.get("session_group_id"), (Long) sessionJson.get("admin_session_group_id"),
getPermissions((JSONObject) sessionJson.get("permissions")),
startTime==null?null:new MillisecondsTimePoint(startTime.longValue()),
endTime==null?null:new MillisecondsTimePoint(endTime.longValue()),
blob == null ? false : blob, conn);
}
}
@@ -0,0 +1,83 @@
package com.sap.sailing.domain.igtimiadapter.impl;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
import com.sap.sailing.domain.igtimiadapter.Permission;
import com.sap.sailing.domain.igtimiadapter.Session;
import com.sap.sailing.domain.igtimiadapter.User;
public class SessionImpl extends HasIdImpl implements Session {
private final long ownerId;
private final String name;
private final long sessionGroupId;
private final long adminSessionGroupId;
private final Iterable<Permission> permissions;
private final TimePoint startTime;
private final TimePoint endTime;
private final boolean blob;
private final IgtimiConnection conn;
protected SessionImpl(long id, String name, long ownerId, long sessionGroupId,
long adminSessionGroupId, Iterable<Permission> permissions, TimePoint startTime, TimePoint endTime, boolean blob, IgtimiConnection conn) {
super(id);
this.name = name;
this.ownerId = ownerId;
this.sessionGroupId = sessionGroupId;
this.adminSessionGroupId = adminSessionGroupId;
this.permissions = permissions;
this.startTime = startTime;
this.endTime = endTime;
this.blob = blob;
this.conn = conn;
}
@Override
public String getName() {
return name;
}
@Override
public long getOwnerId() {
return ownerId;
}
@Override
public long getSessionGroupId() {
return sessionGroupId;
}
@Override
public long getAdminSessionGroupId() {
return adminSessionGroupId;
}
@Override
public Iterable<Permission> getPermissions() {
return permissions;
}
@Override
public TimePoint getEndTime() {
return endTime;
}
@Override
public TimePoint getStartTime() {
return startTime;
}
@Override
public boolean isBlob() {
return blob;
}
@Override
public User getOwner() throws IllegalStateException, ClientProtocolException, IOException, ParseException {
return conn.getUser(getOwnerId());
}
}
@@ -7,12 +7,11 @@ import com.sap.sailing.domain.igtimiadapter.User;
public class UserDeserializer {
public User createUserFromJson(JSONObject userJson) {
Boolean blob = (Boolean) userJson.get("blob");
return new UserImpl((Long) userJson.get("id"),
final Long id = (Long) userJson.get("id");
return new UserImpl(id,
(String) userJson.get("first_name"),
(String) userJson.get("surname"),
(String) userJson.get("email"),
blob == null ? false : blob);
}
}
@@ -17,8 +17,8 @@ import javax.ws.rs.core.UriInfo;
import org.apache.http.client.ClientProtocolException;
import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.impl.Activator;
import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl;
@Path(AuthorizationCallback.V1)
public class AuthorizationCallback {
@@ -27,7 +27,7 @@ public class AuthorizationCallback {
private static final String AUTHORIZATIONCALLBACK = "/authorizationcallback";
static final String V1 = "/v1";
static final String V1_AUTHORIZATIONCALLBACK = V1 + AUTHORIZATIONCALLBACK;
private final IgtimiConnectionFactory connectionFactory;
private final IgtimiConnectionFactoryImpl connectionFactory;
public AuthorizationCallback() throws ClientProtocolException, IllegalStateException, IOException, ParseException {
connectionFactory = Activator.getInstance().getConnectionFactory();
@@ -5,10 +5,9 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -26,14 +25,15 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory;
import com.sap.sailing.domain.igtimiadapter.LiveDataListener;
import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver;
import com.sap.sailing.domain.igtimiadapter.LiveDataConnection;
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
import com.sap.sailing.domain.igtimiadapter.impl.FixFactory;
import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl;
public class WebSocketConnectionManager extends WebSocketAdapter {
public class WebSocketConnectionManager extends WebSocketAdapter implements LiveDataConnection {
private static final Logger logger = Logger.getLogger(WebSocketConnectionManager.class.getName());
private final IgtimiConnectionFactory connectionFactory;
private final IgtimiConnectionFactoryImpl connectionFactory;
private static enum TargetState { OPEN, CLOSED };
private TargetState targetState;
private final WebSocketClient client;
@@ -44,19 +44,19 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
private final Account account;
private final FixFactory fixFactory;
private boolean receivedServerHeartbeatInInterval;
private final Set<LiveDataListener> liveDataListeners;
private final ConcurrentHashMap<BulkFixReceiver, BulkFixReceiver> listeners;
private TimePoint igtimiServerTimepoint;
private TimePoint localTimepointWhenServerTimepointWasReceived;
public WebSocketConnectionManager(IgtimiConnectionFactory connectionFactory, Iterable<String> deviceIds, Account account) throws Exception {
this.timer = new Timer("Timer for WebSocketConnectionManager for units "+deviceIds+" and account "+account);
this.deviceIds = deviceIds;
public WebSocketConnectionManager(IgtimiConnectionFactoryImpl connectionFactory, Iterable<String> deviceSerialNumbers, Account account) throws Exception {
this.timer = new Timer("Timer for WebSocketConnectionManager for units "+deviceSerialNumbers+" and account "+account);
this.deviceIds = deviceSerialNumbers;
this.account = account;
this.fixFactory = new FixFactory();
this.connectionFactory = connectionFactory;
this.liveDataListeners = new ConcurrentSkipListSet<>();
this.listeners = new ConcurrentHashMap<>();
client = new WebSocketClient();
configurationMessage = connectionFactory.getWebSocketConfigurationMessage(account, deviceIds);
configurationMessage = connectionFactory.getWebSocketConfigurationMessage(account, deviceSerialNumbers);
request = new ClientUpgradeRequest();
client.start();
reconnect();
@@ -71,6 +71,7 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
* use 0 to wait indefinitely
* @return <code>true</code> if the connection is established before the timeout occurred
*/
@Override
public boolean waitForConnection(long timeoutInMillis) throws InterruptedException {
long startedToWait = System.currentTimeMillis();
synchronized (this) {
@@ -81,10 +82,6 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
}
}
public void addLiveDataListener(LiveDataListener listener) {
liveDataListeners.add(listener);
}
public void disconnect() throws Exception {
targetState = TargetState.CLOSED;
client.stop();
@@ -141,9 +138,14 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
return new Pair<TimePoint, TimePoint>(igtimiServerTimepoint, localTimepointWhenServerTimepointWasReceived);
}
@Override
public void addListener(BulkFixReceiver listener) {
listeners.put(listener, listener);
}
private void notifyListeners(List<Fix> fixes) {
for (LiveDataListener listener : liveDataListeners) {
listener.fixesReceived(fixes);
for (BulkFixReceiver listener : listeners.keySet()) {
listener.received(fixes);
}
}
@@ -198,7 +200,7 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
}, /* delay */ 0, /* send ping message every other minute; has to be at least every five minutes, so this should be safe */ 120000l);
}
public void reconnect() throws IOException, IllegalStateException, ParseException, URISyntaxException {
private void reconnect() throws IOException, IllegalStateException, ParseException, URISyntaxException {
IOException lastException = null;
for (URI uri : connectionFactory.getWebsocketServers()) {
try {
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.persistence;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import com.mongodb.DBObject;
import com.sap.sailing.domain.base.Competitor;
@@ -10,6 +11,8 @@ import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RegattaRegistry;
import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.leaderboard.Leaderboard;
@@ -97,4 +100,6 @@ public interface DomainObjectFactory {
Collection<Competitor> loadAllCompetitors();
DomainFactory getBaseDomainFactory();
Iterable<Entry<DeviceConfigurationMatcher, DeviceConfiguration>> loadAllDeviceConfigurations();
}
@@ -9,6 +9,8 @@ import com.sap.sailing.domain.base.Event;
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.configuration.DeviceConfiguration;
import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
@@ -102,4 +104,8 @@ public interface MongoObjectFactory {
DB getDatabase();
void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration);
void removeDeviceConfiguration(DeviceConfigurationMatcher matcher);
}
@@ -51,10 +51,16 @@ public enum CollectionNames {
/**
* Stores the race log events for a tracked race.
*/
RACE_LOGS,
/**
* Stores competitors for smartphone tracking.
*/
COMPETITORS,
RACE_LOGS,
/**
* Stores configurations for mobile devices.
*/
CONFIGURATIONS;
}
@@ -7,6 +7,7 @@ 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 java.util.UUID;
import java.util.logging.Level;
@@ -15,6 +16,7 @@ import java.util.logging.Logger;
import org.bson.types.ObjectId;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
@@ -41,6 +43,11 @@ import com.sap.sailing.domain.base.RegattaRegistry;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Venue;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationImpl;
import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl;
import com.sap.sailing.domain.base.impl.CourseDataImpl;
import com.sap.sailing.domain.base.impl.EventImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
@@ -61,6 +68,7 @@ import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
@@ -72,7 +80,7 @@ import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.racelog.StartProcedureType;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections;
import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections.LeaderboardCorrectionsResolvedListener;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
@@ -90,10 +98,12 @@ import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRul
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
import com.sap.sailing.domain.racelog.CompetitorResults;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
import com.sap.sailing.domain.racelog.RaceLogEventRestoreFactory;
import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent;
import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent;
@@ -108,14 +118,20 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
import com.sap.sailing.domain.racelog.impl.CompetitorResultsImpl;
import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl;
import com.sap.sailing.domain.racelog.impl.RaceLogImpl;
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.CompetitorJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.DeviceConfigurationJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.Helpers;
import com.sap.sailing.server.gateway.deserialization.impl.RegattaConfigurationJsonDeserializer;
public class DomainObjectFactoryImpl implements DomainObjectFactory {
private static final Logger logger = Logger.getLogger(DomainObjectFactoryImpl.class.getName());
@@ -839,7 +855,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
private CourseArea loadCourseArea(DBObject courseAreaDBObject) {
String name = (String) courseAreaDBObject.get(FieldNames.COURSE_AREA_NAME.name());
Serializable id = (Serializable) courseAreaDBObject.get(FieldNames.COURSE_AREA_ID.name());
UUID id = (UUID) courseAreaDBObject.get(FieldNames.COURSE_AREA_ID.name());
return baseDomainFactory.getOrCreateCourseArea(id, name);
}
@@ -890,7 +906,18 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
courseArea = baseDomainFactory.getExistingCourseAreaById(courseAreaUuid);
}
RegattaConfiguration configuration = null;
if (dbRegatta.containsField(FieldNames.REGATTA_REGATTA_CONFIGURATION.name())) {
try {
JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(dbRegatta.get(FieldNames.REGATTA_REGATTA_CONFIGURATION.name()))));
configuration = RegattaConfigurationJsonDeserializer.create().deserialize(json);
} catch (JsonDeserializationException|ParseException e) {
logger.log(Level.WARNING, "Error loading racing procedure configration for regatta.", e);
}
}
result = new RegattaImpl(raceLogStore, baseName, boatClass, series, /* persistent */ true, loadScoringScheme(dbRegatta), id, courseArea);
result.setRegattaConfiguration(configuration);
}
return result;
}
@@ -1022,9 +1049,19 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(identifier.getIdentifier()));
loadRaceLogEvents(result, query);
// query for events with deprecated identifier format
// query for events with deprecated identifier format...
query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(identifier.getDeprecatedIdentifier()));
loadRaceLogEvents(result, query);
List<RaceLogEvent> eventsToMigrate = loadRaceLogEvents(result, query);
if (!eventsToMigrate.isEmpty()) {
// ... migrate them...
MongoRaceLogStoreVisitor storeVisitor = new MongoRaceLogStoreVisitor(identifier, new MongoObjectFactoryImpl(database));
for (RaceLogEvent event : eventsToMigrate) {
event.accept(storeVisitor);
}
// ... and delete the old ones...
database.getCollection(CollectionNames.RACE_LOGS.name()).remove(query);
}
} catch (Throwable t) {
// something went wrong during DB access; report, then use empty new race log
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded race log data. Check MongoDB settings.");
@@ -1033,126 +1070,141 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return result;
}
private void loadRaceLogEvents(RaceLog result, BasicDBObject query) {
private List<RaceLogEvent> loadRaceLogEvents(RaceLog targetRaceLog, BasicDBObject query) {
List<RaceLogEvent> result = new ArrayList<>();
DBCollection raceLog = database.getCollection(CollectionNames.RACE_LOGS.name());
for (DBObject o : raceLog.find(query)) {
RaceLogEvent raceLogEvent = loadRaceLogEvent((DBObject) o.get(FieldNames.RACE_LOG_EVENT.name()));
if (raceLogEvent != null) {
result.load(raceLogEvent);
targetRaceLog.load(raceLogEvent);
result.add(raceLogEvent);
}
}
return result;
}
public RaceLogEvent loadRaceLogEvent(DBObject dbObject) {
TimePoint timePoint = loadTimePoint(dbObject);
TimePoint logicalTimePoint = loadTimePoint(dbObject);
TimePoint createdAt = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_CREATED_AT);
Serializable id = (Serializable) dbObject.get(FieldNames.RACE_LOG_EVENT_ID.name());
Integer passId = (Integer) dbObject.get(FieldNames.RACE_LOG_EVENT_PASS_ID.name());
BasicDBList dbCompetitors = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name());
List<Competitor> competitors = loadCompetitorsForRaceLogEvent(dbCompetitors);
final RaceLogEventAuthor author;
String authorName = (String) dbObject.get(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name());
Number authorPriority = (Number) dbObject.get(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name());
if (authorName != null && authorPriority != null) {
author = new RaceLogEventAuthorImpl(authorName, authorPriority.intValue());
} else {
author = RaceLogEventAuthorImpl.createCompatibilityAuthor();
}
String eventClass = (String) dbObject.get(FieldNames.RACE_LOG_EVENT_CLASS.name());
if (eventClass.equals(RaceLogStartTimeEvent.class.getSimpleName())) {
return loadRaceLogStartTimeEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogStartTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogRaceStatusEvent.class.getSimpleName())) {
return loadRaceLogRaceStatusEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogRaceStatusEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogFlagEvent.class.getSimpleName())) {
return loadRaceLogFlagEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogFlagEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogPassChangeEvent.class.getSimpleName())) {
return loadRaceLogPassChangeEvent(createdAt, timePoint, id, passId, competitors);
return loadRaceLogPassChangeEvent(createdAt, author, logicalTimePoint, id, passId, competitors);
} else if (eventClass.equals(RaceLogCourseAreaChangedEvent.class.getSimpleName())) {
return loadRaceLogCourseAreaChangedEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogCourseDesignChangedEvent.class.getSimpleName())) {
return loadRaceLogCourseDesignChangedEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogCourseDesignChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogFinishPositioningListChangedEvent.class.getSimpleName())) {
return loadRaceLogFinishPositioningListChangedEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogFinishPositioningListChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogFinishPositioningConfirmedEvent.class.getSimpleName())) {
return loadRaceLogFinishPositioningConfirmedEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogFinishPositioningConfirmedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogPathfinderEvent.class.getSimpleName())) {
return loadRaceLogPathfinderEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogPathfinderEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogGateLineOpeningTimeEvent.class.getSimpleName())) {
return loadRaceLogGateLineOpeningTimeEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogGateLineOpeningTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogStartProcedureChangedEvent.class.getSimpleName())) {
return loadRaceLogStartProcedureChangedEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogStartProcedureChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogProtestStartTimeEvent.class.getSimpleName())) {
return loadRaceLogRaceLogProtestStartTimeEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogRaceLogProtestStartTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
} else if (eventClass.equals(RaceLogWindFixEvent.class.getSimpleName())) {
return loadRaceLogRaceLogWindFixEvent(createdAt, timePoint, id, passId, competitors, dbObject);
return loadRaceLogRaceLogWindFixEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject);
}
throw new IllegalStateException(String.format("Unknown RaceLogEvent type %s", eventClass));
}
private RaceLogEvent loadRaceLogRaceLogWindFixEvent(TimePoint createdAt, TimePoint timePoint, Serializable id,
Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogEvent loadRaceLogRaceLogWindFixEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint,
Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
Wind wind = loadWind((DBObject) dbObject.get(FieldNames.WIND.name()));
return raceLogEventFactory.createWindFixEvent(createdAt, timePoint, id, competitors, passId, wind);
return raceLogEventFactory.createWindFixEvent(createdAt, author, logicalTimePoint, id, competitors, passId, wind);
}
private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, TimePoint timePoint,
Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author,
TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
TimePoint protestStartTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_PROTEST_START_TIME);
return raceLogEventFactory.createProtestStartTimeEvent(createdAt, timePoint, id, competitors, passId, protestStartTime);
return raceLogEventFactory.createProtestStartTimeEvent(createdAt, author, logicalTimePoint, id, competitors, passId, protestStartTime);
}
private RaceLogEvent loadRaceLogStartProcedureChangedEvent(TimePoint createdAt, TimePoint timePoint,
private RaceLogEvent loadRaceLogStartProcedureChangedEvent(TimePoint createdAt, RaceLogEventAuthor author,
TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
RacingProcedureType type = RacingProcedureType.valueOf(dbObject.get(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name()).toString());
return raceLogEventFactory.createStartProcedureChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, type);
}
private RaceLogEvent loadRaceLogGateLineOpeningTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint,
Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
StartProcedureType type = StartProcedureType.valueOf(dbObject.get(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name()).toString());
return raceLogEventFactory.createStartProcedureChangedEvent(createdAt, timePoint, id, competitors, passId, type);
}
private RaceLogEvent loadRaceLogGateLineOpeningTimeEvent(TimePoint createdAt, TimePoint timePoint, Serializable id,
Integer passId, List<Competitor> competitors, DBObject dbObject) {
Number gateLineOpeningTime = (Number) dbObject.get(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name());
return raceLogEventFactory.createGateLineOpeningTimeEvent(createdAt, timePoint, id, competitors, passId,
gateLineOpeningTime == null ? null : gateLineOpeningTime.longValue());
Number gateLaunchStopTime = (Number) dbObject.get(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name());
Number golfDownTime = 0;
if (dbObject.containsField(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name())) {
golfDownTime = (Number) dbObject.get(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name());
}
return raceLogEventFactory.createGateLineOpeningTimeEvent(createdAt, author, logicalTimePoint, id, competitors,
passId, gateLaunchStopTime == null ? null : gateLaunchStopTime.longValue(), golfDownTime.longValue());
}
private RaceLogEvent loadRaceLogPathfinderEvent(TimePoint createdAt, TimePoint timePoint, Serializable id,
Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogEvent loadRaceLogPathfinderEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint,
Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
String pathfinderId = dbObject.get(FieldNames.RACE_LOG_PATHFINDER_ID.name()).toString();
return raceLogEventFactory.createPathfinderEvent(createdAt, timePoint, id, competitors, passId, pathfinderId);
return raceLogEventFactory.createPathfinderEvent(createdAt, author, logicalTimePoint, id, competitors, passId, pathfinderId);
}
private RaceLogEvent loadRaceLogFinishPositioningConfirmedEvent(TimePoint createdAt, TimePoint timePoint,
Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogEvent loadRaceLogFinishPositioningConfirmedEvent(TimePoint createdAt, RaceLogEventAuthor author,
TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
BasicDBList dbPositionedCompetitorList = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name());
List<Triple<Serializable, String, MaxPointsReason>> positionedCompetitors = null;
CompetitorResults positionedCompetitors = null;
//When a confirmation event is loaded that does not contain the positioned competitors (this is the case for the ESS events in
//Singapore and Quingdao) then null should be set for the positionedCompetitors, which is evaluated later on.
if (dbPositionedCompetitorList != null) {
positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList);
}
return raceLogEventFactory.createFinishPositioningConfirmedEvent(createdAt, timePoint, id, competitors, passId, positionedCompetitors);
return raceLogEventFactory.createFinishPositioningConfirmedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors);
}
private RaceLogEvent loadRaceLogFinishPositioningListChangedEvent(TimePoint createdAt, TimePoint timePoint,
Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogEvent loadRaceLogFinishPositioningListChangedEvent(TimePoint createdAt, RaceLogEventAuthor author,
TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
BasicDBList dbPositionedCompetitorList = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name());
List<Triple<Serializable, String, MaxPointsReason>> positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList);
CompetitorResults positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList);
return raceLogEventFactory.createFinishPositioningListChangedEvent(createdAt, timePoint, id, competitors, passId, positionedCompetitors);
return raceLogEventFactory.createFinishPositioningListChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors);
}
private RaceLogEvent loadRaceLogPassChangeEvent(TimePoint createdAt, TimePoint timePoint, Serializable id,
Integer passId, List<Competitor> competitors) {
return raceLogEventFactory.createPassChangeEvent(createdAt, timePoint, id, competitors, passId);
private RaceLogEvent loadRaceLogPassChangeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint,
Serializable id, Integer passId, List<Competitor> competitors) {
return raceLogEventFactory.createPassChangeEvent(createdAt, author, logicalTimePoint, id, competitors, passId);
}
private RaceLogCourseDesignChangedEvent loadRaceLogCourseDesignChangedEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogCourseDesignChangedEvent loadRaceLogCourseDesignChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
String courseName = (String) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name());
CourseBase courseData = loadCourseData((BasicDBList) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN.name()), courseName);
return raceLogEventFactory.createCourseDesignChangedEvent(createdAt, timePoint, id, competitors, passId, courseData);
return raceLogEventFactory.createCourseDesignChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, courseData);
}
private RaceLogCourseAreaChangedEvent loadRaceLogCourseAreaChangedEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogCourseAreaChangedEvent loadRaceLogCourseAreaChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
Serializable courseAreaId = (Serializable) dbObject.get(FieldNames.COURSE_AREA_ID.name());
return raceLogEventFactory.createCourseAreaChangedEvent(createdAt, timePoint, id, competitors, passId, courseAreaId);
return raceLogEventFactory.createCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, courseAreaId);
}
private List<Triple<Serializable, String, MaxPointsReason>> loadPositionedCompetitors(BasicDBList dbPositionedCompetitorList) {
List<Triple<Serializable, String, MaxPointsReason>> positionedCompetitors = new ArrayList<Triple<Serializable, String, MaxPointsReason>>();
private CompetitorResults loadPositionedCompetitors(BasicDBList dbPositionedCompetitorList) {
CompetitorResults positionedCompetitors = new CompetitorResultsImpl();
for (Object object : dbPositionedCompetitorList) {
DBObject dbObject = (DBObject) object;
@@ -1181,13 +1233,13 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
List<Competitor> competitors = new ArrayList<Competitor>();
for (Object object : dbCompetitorList) {
Serializable competitorId = (Serializable) object;
Competitor competitor = baseDomainFactory.getExistingCompetitorById(competitorId);
Competitor competitor = baseDomainFactory.getCompetitorStore().getExistingCompetitorById(competitorId);
competitors.add(competitor);
}
return competitors;
}
private RaceLogFlagEvent loadRaceLogFlagEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogFlagEvent loadRaceLogFlagEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
Flags upperFlag = Flags.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name()));
Flags lowerFlag = Flags.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name()));
Boolean displayed = Boolean.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_DISPLAYED.name()));
@@ -1196,17 +1248,17 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return null;
}
return raceLogEventFactory.createFlagEvent(createdAt, timePoint, id, competitors, passId, upperFlag, lowerFlag, displayed);
return raceLogEventFactory.createFlagEvent(createdAt, author, logicalTimePoint, id, competitors, passId, upperFlag, lowerFlag, displayed);
}
private RaceLogStartTimeEvent loadRaceLogStartTimeEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogStartTimeEvent loadRaceLogStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
TimePoint startTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_START_TIME);
return raceLogEventFactory.createStartTimeEvent(createdAt, timePoint, id, competitors, passId, startTime);
return raceLogEventFactory.createStartTimeEvent(createdAt, author, logicalTimePoint, id, competitors, passId, startTime);
}
private RaceLogRaceStatusEvent loadRaceLogRaceStatusEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
private RaceLogRaceStatusEvent loadRaceLogRaceStatusEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
RaceLogRaceStatus nextStatus = RaceLogRaceStatus.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name()));
return raceLogEventFactory.createRaceStatusEvent(createdAt, timePoint, id, competitors, passId, nextStatus);
return raceLogEventFactory.createRaceStatusEvent(createdAt, author, logicalTimePoint, id, competitors, passId, nextStatus);
}
private CourseBase loadCourseData(BasicDBList dbCourseList, String courseName) {
@@ -1287,4 +1339,56 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
return result;
}
@Override
public Iterable<Entry<DeviceConfigurationMatcher, DeviceConfiguration>> loadAllDeviceConfigurations() {
Map<DeviceConfigurationMatcher, DeviceConfiguration> result = new HashMap<>();
DBCollection configurationCollection = database.getCollection(CollectionNames.CONFIGURATIONS.name());
try {
for (DBObject dbObject : configurationCollection.find()) {
Pair<DeviceConfigurationMatcher, DeviceConfiguration> entry = loadConfigurationEntry(dbObject);
result.put(entry.getA(), entry.getB());
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load configurations.");
logger.log(Level.SEVERE, "loadAllDeviceConfigurations", e);
}
return result.entrySet();
}
private Pair<DeviceConfigurationMatcher, DeviceConfiguration> loadConfigurationEntry(DBObject dbObject) {
DBObject matcherObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_MATCHER.name());
DBObject configObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_CONFIG.name());
return new Pair<DeviceConfigurationMatcher, DeviceConfiguration>(loadConfigurationMatcher(matcherObject),
loadConfiguration(configObject));
}
private DeviceConfigurationMatcher loadConfigurationMatcher(DBObject matcherObject) {
DeviceConfigurationMatcherType type = DeviceConfigurationMatcherType.valueOf(
matcherObject.get(FieldNames.CONFIGURATION_MATCHER_TYPE.name()).toString());
List<String> clientIdentifiers = new ArrayList<String>();
BasicDBList clientIdentifiersObject = (BasicDBList) matcherObject.get(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name());
if (clientIdentifiersObject != null) {
for (Object clientIdentifier : clientIdentifiersObject) {
clientIdentifiers.add(clientIdentifier.toString());
}
}
return baseDomainFactory.getOrCreateDeviceConfigurationMatcher(type, clientIdentifiers);
}
private DeviceConfiguration loadConfiguration(DBObject configObject) {
DeviceConfiguration configuration = null;
try {
JsonDeserializer<DeviceConfiguration> deserializer = DeviceConfigurationJsonDeserializer.create();
JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(configObject)));
configuration = deserializer.deserialize(json);
} catch (JsonDeserializationException | ParseException e) {
logger.log(Level.SEVERE, "Error parsing configuration object from MongoDB, falling back to empty configuration.");
logger.log(Level.SEVERE, "loadConfiguration", e);
configuration = new DeviceConfigurationImpl(new RegattaConfigurationImpl());
}
return configuration;
}
}
@@ -18,6 +18,7 @@ public enum FieldNames {
REGATTA_ID,
REGATTA_BASE_NAME,
REGATTA_SERIES,
REGATTA_REGATTA_CONFIGURATION,
SERIES_NAME,
SERIES_IS_MEDAL,
SERIES_STARTS_WITH_ZERO_SCORE,
@@ -69,9 +70,16 @@ public enum FieldNames {
// race log
RACE_LOG_IDENTIFIER,
RACE_LOG_EVENT, RACE_LOG_EVENT_PASS_ID, RACE_LOG_EVENT_ID, RACE_LOG_EVENT_CREATED_AT, RACE_LOG_EVENT_INVOLVED_BOATS, RACE_LOG_EVENT_FLAG_UPPER, RACE_LOG_EVENT_FLAG_LOWER,
RACE_LOG_EVENT, RACE_LOG_EVENT_PASS_ID, RACE_LOG_EVENT_ID, RACE_LOG_EVENT_CREATED_AT, RACE_LOG_EVENT_AUTHOR_NAME, RACE_LOG_EVENT_AUTHOR_PRIORITY,
RACE_LOG_EVENT_INVOLVED_BOATS, RACE_LOG_EVENT_FLAG_UPPER, RACE_LOG_EVENT_FLAG_LOWER,
RACE_LOG_EVENT_FLAG_DISPLAYED, RACE_LOG_EVENT_START_TIME, RACE_LOG_EVENT_CLASS, RACE_LOG_EVENT_NEXT_STATUS, RACE_LOG_COURSE_DESIGN, WAYPOINT_ID,
WAYPOINT_NAME, WAYPOINT_PASSINGSIDE, CONTROLPOINT, CONTROLPOINT_CLASS, MARK_ID, MARK_COLOR, MARK_NAME, MARK_PATTERN, MARK_SHAPE, MARK_TYPE,
CONTROLPOINT_VALUE, GATE_ID, GATE_NAME, GATE_LEFT, GATE_RIGHT, RACE_LOG_POSITIONED_COMPETITORS, RACE_LOG_PATHFINDER_ID, RACE_LOG_GATE_LINE_OPENING_TIME,
RACE_LOG_START_PROCEDURE_TYPE, RACE_LOG_PROTEST_START_TIME, RACE_LOG_COURSE_DESIGN_NAME;
CONTROLPOINT_VALUE, GATE_ID, GATE_NAME, GATE_LEFT, GATE_RIGHT, RACE_LOG_POSITIONED_COMPETITORS, RACE_LOG_PATHFINDER_ID, RACE_LOG_GATE_LINE_OPENING_TIME,
RACE_LOG_GOLF_DOWN_TIME,
RACE_LOG_START_PROCEDURE_TYPE, RACE_LOG_PROTEST_START_TIME, RACE_LOG_COURSE_DESIGN_NAME,
// configurations
CONFIGURATION_MATCHER, CONFIGURATION_CONFIG, CONFIGURATION_MATCHER_ID, CONFIGURATION_MATCHER_TYPE, CONFIGURATION_MATCHER_CLIENTS,
CONFIGURATION_CONFIG_ALLOWED_COURSE_AREA_NAMES, CONFIGURATION_CONFIG_MIN_ROUNDS, CONFIGURATION_CONFIG_MAX_ROUNDS, CONFIGURATION_CONFIG_RESULTS_MAIL,
CONFIGURATION_CONFIG_PROCEDURES;
}
@@ -29,6 +29,11 @@ import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Timed;
import com.sap.sailing.domain.base.Venue;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherMulti;
import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherSingle;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.MaxPointsReason;
@@ -39,6 +44,7 @@ import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
@@ -51,6 +57,7 @@ import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent;
import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
@@ -68,7 +75,10 @@ import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.DeviceConfigurationJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.RegattaConfigurationJsonSerializer;
public class MongoObjectFactoryImpl implements MongoObjectFactory {
private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName());
@@ -467,6 +477,12 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
} else {
dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), null);
}
if (regatta.getRegattaConfiguration() != null) {
JsonSerializer<RegattaConfiguration> serializer = RegattaConfigurationJsonSerializer.create();
JSONObject json = serializer.serialize(regatta.getRegattaConfiguration());
DBObject configurationObject = (DBObject) JSON.parse(json.toString());
dbRegatta.put(FieldNames.REGATTA_REGATTA_CONFIGURATION.name(), configurationObject);
}
regattasCollection.update(query, dbRegatta, /* upsrt */ true, /* multi */ false);
}
@@ -545,6 +561,13 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
result.ensureIndex(new BasicDBObject(FieldNames.RACE_LOG_IDENTIFIER.name(), null));
return result;
}
private void storeRaceLogEventAuthor(DBObject dbObject, RaceLogEventAuthor author) {
if (author != null) {
dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name(), author.getName());
dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name(), author.getPriority());
}
}
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFlagEvent flagEvent) {
BasicDBObject result = new BasicDBObject();
@@ -702,11 +725,13 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
}
private void storeRaceLogEventProperties(RaceLogEvent event, DBObject result) {
storeTimed(event, result);
// for compatibility reasons we reuse the field name of Timed
storeTimePoint(event.getLogicalTimePoint(), result, FieldNames.TIME_AS_MILLIS);
storeTimePoint(event.getCreatedAt(), result, FieldNames.RACE_LOG_EVENT_CREATED_AT);
result.put(FieldNames.RACE_LOG_EVENT_ID.name(), event.getId());
result.put(FieldNames.RACE_LOG_EVENT_PASS_ID.name(), event.getPassId());
result.put(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name(), storeInvolvedBoatsForRaceLogEvent(event.getInvolvedBoats()));
storeRaceLogEventAuthor(result, event.getAuthor());
}
@@ -785,7 +810,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogGateLineOpeningTimeEvent.class.getSimpleName());
result.put(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTime());
result.put(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGateLaunchStopTime());
result.put(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGolfDownTime());
return result;
}
@@ -871,7 +897,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
public void storeCompetitor(Competitor competitor) {
DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
JSONObject json = competitorSerializer.serialize(competitor);
BasicDBObject query = new BasicDBObject(CompetitorJsonSerializer.FIELD_ID, competitor.getId());
DBObject query = (DBObject) JSON.parse(competitorSerializer.getCompetitorIdQuery(competitor).toString());
DBObject entry = (DBObject) JSON.parse(json.toString());
collection.update(query, entry, /* upsrt */true, /* multi */false);
}
@@ -887,7 +913,53 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
public void removeCompetitor(Competitor competitor) {
logger.info("Removing persistent competitor info for competitor "+competitor.getName()+" with ID "+competitor.getId());
DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
BasicDBObject query = new BasicDBObject(CompetitorJsonSerializer.FIELD_ID, competitor.getId().toString());
DBObject query = (DBObject) JSON.parse(competitorSerializer.getCompetitorIdQuery(competitor).toString());
collection.remove(query);
}
@Override
public void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) {
DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name());
DBObject query = new BasicDBObject();
query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
DBObject entryObject = new BasicDBObject();
entryObject.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
entryObject.put(FieldNames.CONFIGURATION_MATCHER.name(), createDeviceConfigurationMatcherObject(matcher));
entryObject.put(FieldNames.CONFIGURATION_CONFIG.name(), createDeviceConfigurationObject(configuration));
configurationsCollections.update(query, entryObject, /* upsrt */ true, /* multi */ false);
}
private DBObject createDeviceConfigurationMatcherObject(DeviceConfigurationMatcher matcher) {
DBObject matcherObject = new BasicDBObject();
matcherObject.put(FieldNames.CONFIGURATION_MATCHER_TYPE.name(), matcher.getMatcherType().name());
if (matcher instanceof DeviceConfigurationMatcherSingle) {
BasicDBList client = new BasicDBList();
client.add(((DeviceConfigurationMatcherSingle)matcher).getClientIdentifier());
matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), client);
} else if (matcher instanceof DeviceConfigurationMatcherMulti) {
BasicDBList clients = new BasicDBList();
Util.addAll(((DeviceConfigurationMatcherMulti)matcher).getClientIdentifiers(), clients);
matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), clients);
}
return matcherObject;
}
private DBObject createDeviceConfigurationObject(DeviceConfiguration configuration) {
JsonSerializer<DeviceConfiguration> serializer = DeviceConfigurationJsonSerializer.create();
JSONObject json = serializer.serialize(configuration);
DBObject entry = (DBObject) JSON.parse(json.toString());
return entry;
}
@Override
public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) {
DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name());
DBObject query = new BasicDBObject();
query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
configurationsCollections.remove(query);
}
}
@@ -15,25 +15,27 @@ public class MongoRaceLogStoreImpl implements RaceLogStore {
private final MongoObjectFactory mongoObjectFactory;
private final DomainObjectFactory domainObjectFactory;
private final Map<RaceLogIdentifier, RaceLog> raceLogCache;
private final WeakHashMap<RaceLog, MongoRaceLogListener> listeners;
private final WeakHashMap<RaceLog, MongoRaceLogStoreVisitor> listeners;
public MongoRaceLogStoreImpl(MongoObjectFactory mongoObjectFactory, DomainObjectFactory domainObjectFactory) {
this.mongoObjectFactory = mongoObjectFactory;
this.domainObjectFactory = domainObjectFactory;
this.raceLogCache = new HashMap<>();
this.listeners = new WeakHashMap<RaceLog, MongoRaceLogListener>();
this.listeners = new WeakHashMap<RaceLog, MongoRaceLogStoreVisitor>();
}
@Override
public RaceLog getRaceLog(RaceLogIdentifier identifier, boolean ignoreCache) {
final RaceLog result;
if (!ignoreCache && raceLogCache.containsKey(identifier)) {
return raceLogCache.get(identifier);
result = raceLogCache.get(identifier);
} else {
result = domainObjectFactory.loadRaceLog(identifier);
MongoRaceLogStoreVisitor listener = new MongoRaceLogStoreVisitor(identifier, mongoObjectFactory);
listeners.put(result, listener);
result.addListener(listener);
raceLogCache.put(identifier, result);
}
RaceLog result = domainObjectFactory.loadRaceLog(identifier);
MongoRaceLogListener listener = new MongoRaceLogListener(identifier, mongoObjectFactory);
listeners.put(result, listener);
result.addListener(listener);
raceLogCache.put(identifier, result);
return result;
}
@@ -21,15 +21,15 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
public class MongoRaceLogListener implements RaceLogEventVisitor {
public class MongoRaceLogStoreVisitor implements RaceLogEventVisitor {
private final RaceLogIdentifier raceLogIdentifier;
private final MongoObjectFactoryImpl mongoObjectFactory;
private final DBCollection raceLogsCollection;
private final static Logger logger = Logger.getLogger(MongoRaceLogListener.class.getName());
private final static Logger logger = Logger.getLogger(MongoRaceLogStoreVisitor.class.getName());
public MongoRaceLogListener(RaceLogIdentifier identifier, MongoObjectFactory mongoObjectFactory) {
public MongoRaceLogStoreVisitor(RaceLogIdentifier identifier, MongoObjectFactory mongoObjectFactory) {
super();
this.raceLogIdentifier = identifier;
this.mongoObjectFactory = (MongoObjectFactoryImpl) mongoObjectFactory;
@@ -5,21 +5,19 @@ import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.Serializable;
import java.util.List;
import java.util.Arrays;
import org.junit.Test;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.racelog.CompetitorResults;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent;
import com.sap.sailing.domain.racelog.analyzing.impl.ConfirmedFinishPositioningListFinder;
@SuppressWarnings("unchecked")
public class ConfirmedFinishPositioningListFinderTest extends
PassAwareRaceLogAnalyzerTest<ConfirmedFinishPositioningListFinder, List<Triple<Serializable, String, MaxPointsReason>>> {
PassAwareRaceLogAnalyzerTest<ConfirmedFinishPositioningListFinder, CompetitorResults> {
@Override
protected ConfirmedFinishPositioningListFinder createAnalyzer(RaceLog raceLog) {
@@ -27,11 +25,10 @@ public class ConfirmedFinishPositioningListFinderTest extends
}
@Override
protected List<Triple<Serializable, String, MaxPointsReason>> setupTargetEventsForPassAwareTests(int passId) {
RaceLogFinishPositioningConfirmedEvent event = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1, passId);
when(event.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(List.class));
raceLog.add(event);
return event.getPositionedCompetitorsIDsNamesMaxPointsReasons();
protected TargetPair getTargetEventsAndResultForPassAwareTests(int passId, RaceLogEventAuthor author) {
RaceLogFinishPositioningConfirmedEvent event = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1, passId, author);
when(event.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(CompetitorResults.class));
return new TargetPair(Arrays.asList(event), event.getPositionedCompetitorsIDsNamesMaxPointsReasons());
}
@Test
@@ -45,7 +42,7 @@ public class ConfirmedFinishPositioningListFinderTest extends
public void testMostRecent() {
RaceLogFinishPositioningConfirmedEvent event1 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1);
RaceLogFinishPositioningConfirmedEvent event2 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 2);
when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(List.class));
when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(CompetitorResults.class));
raceLog.add(event1);
raceLog.add(event2);
@@ -4,12 +4,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import org.junit.Test;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.racelog.RaceLog;
import com.sap.sailing.domain.racelog.RaceLogEvent;
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent;
import com.sap.sailing.domain.racelog.analyzing.impl.FinishedTimeFinder;
@@ -19,13 +22,12 @@ public class FinishedTimeFinderTest extends PassAwareRaceLogAnalyzerTest<Finishe
protected FinishedTimeFinder createAnalyzer(RaceLog raceLog) {
return new FinishedTimeFinder(raceLog);
}
@Override
protected TimePoint setupTargetEventsForPassAwareTests(int passId) {
RaceLogRaceStatusEvent event = createEvent(RaceLogRaceStatusEvent.class, 1, passId);
protected TargetPair getTargetEventsAndResultForPassAwareTests(int passId, RaceLogEventAuthor author) {
RaceLogRaceStatusEvent event = createEvent(RaceLogRaceStatusEvent.class, 1, passId, author);
when(event.getNextStatus()).thenReturn(RaceLogRaceStatus.FINISHED);
raceLog.add(event);
return event.getTimePoint();
return new TargetPair(Arrays.asList(event), event.getLogicalTimePoint());
}
@Test
@@ -45,7 +47,7 @@ public class FinishedTimeFinderTest extends PassAwareRaceLogAnalyzerTest<Finishe
raceLog.add(event1);
raceLog.add(event2);
assertEquals(event2.getTimePoint(), analyzer.analyze());
assertEquals(event2.getLogicalTimePoint(), analyzer.analyze());
}
@Test
@@ -58,6 +60,6 @@ public class FinishedTimeFinderTest extends PassAwareRaceLogAnalyzerTest<Finishe
raceLog.add(event1);
raceLog.add(event2);
assertEquals(event1.getTimePoint(), analyzer.analyze());
assertEquals(event1.getLogicalTimePoint(), analyzer.analyze());
}
}
Loaded 100 of 987 files, more files were not shown because too many files have changed in this diff. Show more