java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/orc/FailIfNoValidOrcCertificateRule.java
... ...
@@ -15,6 +15,7 @@ import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
15 15
import org.junit.jupiter.api.extension.ExtensionContext;
16 16
17 17
import com.sap.sailing.domain.common.orc.ORCCertificate;
18
+import com.sap.sailing.domain.orc.ORCPublicCertificateDatabase.CertificateFamily;
18 19
import com.sap.sailing.domain.orc.ORCPublicCertificateDatabase.CertificateHandle;
19 20
import com.sap.sailing.domain.orc.ORCPublicCertificateDatabase.CountryOverview;
20 21
import com.sap.sailing.domain.orc.impl.ORCPublicCertificateDatabaseImpl;
... ...
@@ -52,8 +53,9 @@ public class FailIfNoValidOrcCertificateRule implements BeforeTestExecutionCallb
52 53
countryWithMostValidCertificates = StreamSupport
53 54
.stream(db.getCountriesWithValidCertificates().spliterator(), /* parallel */ false)
54 55
.max((c1, c2) -> c1.getCertCount() - c2.getCertCount()).get();
55
- Iterable<CertificateHandle> certificateHandles = db.search(countryWithMostValidCertificates.getIssuingCountry(),
56
- countryWithMostValidCertificates.getVPPYear(), null, null, null, null, /* includeInvalid */ false);
56
+ Iterable<CertificateHandle> certificateHandles = Util.filter(db.search(countryWithMostValidCertificates.getIssuingCountry(),
57
+ countryWithMostValidCertificates.getVPPYear(), null, null, null, null, /* includeInvalid */ false),
58
+ certHandle->certHandle.getFamily() != CertificateFamily.ORC_LIGHT); // exclude LITE certificates
57 59
final List<CertificateHandle> randomSubset = new ArrayList<>();
58 60
Util.addAll(certificateHandles, randomSubset);
59 61
Collections.shuffle(randomSubset);
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/orc/TestORCCertificateImporterJSON.java
... ...
@@ -2,9 +2,11 @@ package com.sap.sailing.domain.orc;
2 2
3 3
import static org.junit.jupiter.api.Assertions.assertNotNull;
4 4
import static org.junit.jupiter.api.Assertions.assertTrue;
5
+import static org.junit.jupiter.api.Assertions.fail;
5 6
6 7
import java.io.IOException;
7 8
import java.util.Collection;
9
+import java.util.logging.Logger;
8 10
9 11
import org.json.simple.parser.ParseException;
10 12
import org.junit.jupiter.api.Test;
... ...
@@ -13,6 +15,8 @@ import com.sap.sailing.domain.common.orc.ORCCertificate;
13 15
14 16
15 17
public class TestORCCertificateImporterJSON extends AbstractORCCertificateImporterTest {
18
+ private static final Logger logger = Logger.getLogger(TestORCCertificateImporterJSON.class.getName());
19
+
16 20
@Test
17 21
public void testSimpleLocalJSONFileRead() throws IOException, ParseException {
18 22
testSimpleLocalFileRead("GER2019.json", "GER20041179");
... ...
@@ -27,9 +31,19 @@ public class TestORCCertificateImporterJSON extends AbstractORCCertificateImport
27 31
@Test
28 32
public void testSimpleOnlineFileRead() throws IOException, ParseException, InterruptedException {
29 33
Collection<ORCCertificate> certificates = FailIfNoValidOrcCertificateRule.getAvailableCerts();
30
- final ORCCertificate referenceCert = certificates.stream().findFirst().get();
31
- assertNotNull(referenceCert);
32
- assertTrue(referenceCert.getWindwardLeewardSpeedPrediction().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificate.NAUTICAL_MILE).asSeconds() > 10);
33
- assertTrue(referenceCert.getLongDistanceSpeedPredictions().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificate.NAUTICAL_MILE).asSeconds() > 10);
34
+ for (final ORCCertificate referenceCert : certificates) {
35
+ assertNotNull(referenceCert);
36
+ // some certificates are not fully filled with allowances for all types of PCS pre-sets; we need to check whether
37
+ // the certificate at hand has those we need for this test; else, we keep going and use another one.
38
+ // We're already excluding LITE certificates to reduce chances for this case
39
+ if (referenceCert.getLongDistanceSpeedPredictions().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]) != null) {
40
+ assertTrue(referenceCert.getWindwardLeewardSpeedPrediction().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificate.NAUTICAL_MILE).asSeconds() > 10);
41
+ assertTrue(referenceCert.getLongDistanceSpeedPredictions().get(ORCCertificate.ALLOWANCES_TRUE_WIND_SPEEDS[0]).getDuration(ORCCertificate.NAUTICAL_MILE).asSeconds() > 10);
42
+ return;
43
+ } else {
44
+ logger.info("No valid GPH found in certificate "+referenceCert.getBoatName()+" with sail number "+referenceCert.getSailNumber());
45
+ }
46
+ }
47
+ fail("Found only certificates with no valid long distance speed predictions; this seems unlikely and is probably an error");
34 48
}
35 49
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/orc/TestORCPublicCertificateDatabase.java
... ...
@@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
5 5
import static org.junit.jupiter.api.Assertions.assertNotNull;
6 6
import static org.junit.jupiter.api.Assertions.assertThrows;
7 7
import static org.junit.jupiter.api.Assertions.assertTrue;
8
+import static org.junit.jupiter.api.Assertions.fail;
8 9
9 10
import java.time.Instant;
10 11
import java.time.LocalDate;
... ...
@@ -153,51 +154,59 @@ public class TestORCPublicCertificateDatabase {
153 154
@Test
154 155
public void testGetCertificate() throws Exception {
155 156
Collection<ORCCertificate> certificates = FailIfNoValidOrcCertificateRule.getAvailableCerts();
156
- final ORCCertificate cert = certificates.stream().findFirst().get();
157
- Iterable<CertificateHandle> certHandles = db.search(/* country */ null, LocalDate.now().getYear(), /* referenceNumber */ null, cert.getBoatName(),
158
- cert.getSailNumber(), /*
159
- * boat class name; could be set to cert.getBoatClassName() but there are
160
- * deviations in ORC DBs and query API, so leaving null:
161
- */ null, /* includeInvalid */ false);
162
- if (Util.isEmpty(certHandles)) {
163
- // there were certs; get one from the previous year
164
- certHandles = db.search(null, LocalDate.now().getYear()-1, null, cert.getBoatName(),
165
- cert.getSailNumber(), /*
166
- * boat class name; could be set to cert.getBoatClassName() but there are
167
- * deviations in ORC DBs and query API, so leaving null:
168
- */ null, /* includeInvalid */ false);
169
- }
170
- if (Util.isEmpty(certHandles)) {
171
- // there were certs; try searching by reference number
172
- certHandles = db.search(null, LocalDate.now().getYear(), cert.getReferenceNumber(), /* boat name may have deviated due to special characters */ null,
173
- cert.getSailNumber(), /*
174
- * boat class name; could be set to cert.getBoatClassName() but there are
175
- * deviations in ORC DBs and query API, so leaving null:
176
- */ null, /* includeInvalid */ false);
177
- }
178
- if (Util.isEmpty(certHandles)) {
179
- // still nothing? Then try by reference number in previous year:
180
- certHandles = db.search(null, LocalDate.now().getYear()-1, cert.getReferenceNumber(), /* boat name may have deviated due to special characters */ null,
181
- cert.getSailNumber(), /*
182
- * boat class name; could be set to cert.getBoatClassName() but there are
183
- * deviations in ORC DBs and query API, so leaving null:
184
- */ null, /* includeInvalid */ false);
157
+ // some certificates seem to be lacking a GPH value; can't use those for this test; we're explicitly excluding LITE certificates already...
158
+ for (final ORCCertificate cert : certificates) {
159
+ if (cert.getGPH() != null) {
160
+ Iterable<CertificateHandle> certHandles = db.search(/* country */ null, LocalDate.now().getYear(), /* referenceNumber */ null, cert.getBoatName(),
161
+ cert.getSailNumber(), /*
162
+ * boat class name; could be set to cert.getBoatClassName() but there are
163
+ * deviations in ORC DBs and query API, so leaving null:
164
+ */ null, /* includeInvalid */ false);
165
+ if (Util.isEmpty(certHandles)) {
166
+ // there were certs; get one from the previous year
167
+ certHandles = db.search(null, LocalDate.now().getYear()-1, null, cert.getBoatName(),
168
+ cert.getSailNumber(), /*
169
+ * boat class name; could be set to cert.getBoatClassName() but there are
170
+ * deviations in ORC DBs and query API, so leaving null:
171
+ */ null, /* includeInvalid */ false);
172
+ }
173
+ if (Util.isEmpty(certHandles)) {
174
+ // there were certs; try searching by reference number
175
+ certHandles = db.search(null, LocalDate.now().getYear(), cert.getReferenceNumber(), /* boat name may have deviated due to special characters */ null,
176
+ cert.getSailNumber(), /*
177
+ * boat class name; could be set to cert.getBoatClassName() but there are
178
+ * deviations in ORC DBs and query API, so leaving null:
179
+ */ null, /* includeInvalid */ false);
180
+ }
181
+ if (Util.isEmpty(certHandles)) {
182
+ // still nothing? Then try by reference number in previous year:
183
+ certHandles = db.search(null, LocalDate.now().getYear()-1, cert.getReferenceNumber(), /* boat name may have deviated due to special characters */ null,
184
+ cert.getSailNumber(), /*
185
+ * boat class name; could be set to cert.getBoatClassName() but there are
186
+ * deviations in ORC DBs and query API, so leaving null:
187
+ */ null, /* includeInvalid */ false);
188
+ }
189
+ Optional<CertificateHandle> certificateHandle = Optional.ofNullable(certHandles.iterator().hasNext() ? certHandles.iterator().next() : null);
190
+ assertTrue(certificateHandle.isPresent(), "No certificate found for handle "+certificateHandle+
191
+ " extracted from certificates "+certificates);
192
+ final String referenceNumber = certificateHandle.get().getReferenceNumber();
193
+ final CertificateHandle handle = db.getCertificateHandle(referenceNumber);
194
+ final ORCCertificate result = db.getCertificate(referenceNumber, handle.getFamily());
195
+ assertNotNull(result, "Unable to load certificate for reference number "+referenceNumber+" from handle "+certificateHandle);
196
+ assertEquals(handle.getGPH(), result.getGPH().asSeconds(), 0.00001);
197
+ // Use some tolerance as we found differences as much as 5s between the dxtDate in the handle coming from the XML search result
198
+ // and the IssueDate field in the JSON. Both suggest to report millisecond accuracy, but dxtDate always seems to have the
199
+ // milliseconds as "000" explaining many sub-second differences. But in some cases differences were significantly bigger.
200
+ assertEquals(handle.getIssueDate().asMillis(), result.getIssueDate().asMillis(), 10000.0, "Issue dates of certificate with reference number "+referenceNumber+
201
+ " varies between current year result handle ("+handle.getIssueDate()+") and certificate ("+
202
+ result.getIssueDate()+").");
203
+ assertEquals(handle.getSailNumber(), result.getSailNumber());
204
+ return;
205
+ } else {
206
+ logger.info("No valid GPH found in certificate "+cert.getBoatName()+" with sail number "+cert.getSailNumber());
207
+ }
185 208
}
186
- Optional<CertificateHandle> certificateHandle = Optional.ofNullable(certHandles.iterator().hasNext() ? certHandles.iterator().next() : null);
187
- assertTrue(certificateHandle.isPresent(), "No certificate found for handle "+certificateHandle+
188
- " extracted from certificates "+certificates);
189
- final String referenceNumber = certificateHandle.get().getReferenceNumber();
190
- final CertificateHandle handle = db.getCertificateHandle(referenceNumber);
191
- final ORCCertificate result = db.getCertificate(referenceNumber, handle.getFamily());
192
- assertNotNull(result, "Unable to load certificate for reference number "+referenceNumber+" from handle "+certificateHandle);
193
- assertEquals(handle.getGPH(), result.getGPH().asSeconds(), 0.00001);
194
- // Use some tolerance as we found differences as much as 5s between the dxtDate in the handle coming from the XML search result
195
- // and the IssueDate field in the JSON. Both suggest to report millisecond accuracy, but dxtDate always seems to have the
196
- // milliseconds as "000" explaining many sub-second differences. But in some cases differences were significantly bigger.
197
- assertEquals(handle.getIssueDate().asMillis(), result.getIssueDate().asMillis(), 10000.0, "Issue dates of certificate with reference number "+referenceNumber+
198
- " varies between current year result handle ("+handle.getIssueDate()+") and certificate ("+
199
- result.getIssueDate()+").");
200
- assertEquals(handle.getSailNumber(), result.getSailNumber());
209
+ fail("No certificate found with a valid GPH; that seems very suspicious and lets this test case fail.");
201 210
}
202 211
203 212
@FailIfNoValidOrcCertificates
java/com.sap.sailing.domain/src/com/sap/sailing/domain/orc/ORCPublicCertificateDatabase.java
... ...
@@ -105,7 +105,7 @@ public interface ORCPublicCertificateDatabase {
105 105
ORCPublicCertificateDatabase INSTANCE = new ORCPublicCertificateDatabaseImpl();
106 106
107 107
public enum CertificateFamily {
108
- UNKNOWN(0, ""), ORC(1, "ORC"), SUPER_YACHT(2, "SY"), DOUBLE_HANDED(3, "DH"), MULTI_HULL(4, "Mu");
108
+ UNKNOWN(0, ""), ORC(1, "ORC"), SUPER_YACHT(2, "SY"), DOUBLE_HANDED(3, "DH"), MULTI_HULL(4, "Mu"), NON_SPINNAKER(5, "NS"), ORC_LIGHT(6, "LITE");
109 109
110 110
private final int familyId;
111 111
private final String familyQueryParamValue;
... ...
@@ -137,8 +137,8 @@ public interface ORCPublicCertificateDatabase {
137 137
}
138 138
139 139
/**
140
- * Data about valid certificates in a country, as obtained, e.g., from http://data.orc.org/public/WPub.dll/RMS. Such a
141
- * record, in its original XML representation, looks like this:
140
+ * Data about valid certificates in a country, as obtained, e.g., from http://data.orc.org/public/WPub.dll/RMS. Such
141
+ * a record, in its original XML representation, looks like this:
142 142
*
143 143
* <pre>
144 144
&lt;CountryId&gt;AUS&lt;/CountryId&gt;
... ...
@@ -152,6 +152,87 @@ public interface ORCPublicCertificateDatabase {
152 152
&lt;RMSCode&gt;CLUB&lt;/RMSCode&gt;
153 153
* </pre>
154 154
*
155
+ * Mappings of family and type work as follows:
156
+ *
157
+ * Used for parsing the ORC public API (WPub.dll) RMS/JSON outputs. Note: Family 6 (ORC Light) certificates
158
+ * generally lack GPH and Performance Curve data required for PCS calculations.
159
+ *
160
+ * <table border="1">
161
+ * <caption>ORC Family and Certificate Type Codes</caption>
162
+ * <tr>
163
+ * <th>Family ID</th>
164
+ * <th>Family Name</th>
165
+ * <th>certType</th>
166
+ * <th>Certificate Name</th>
167
+ * </tr>
168
+ * <tr>
169
+ * <td>1</td>
170
+ * <td>ORC Standard</td>
171
+ * <td>2</td>
172
+ * <td>International (ORCi)</td>
173
+ * </tr>
174
+ * <tr>
175
+ * <td>1</td>
176
+ * <td>ORC Standard</td>
177
+ * <td>3</td>
178
+ * <td>Club</td>
179
+ * </tr>
180
+ * <tr>
181
+ * <td>2</td>
182
+ * <td>Super Yacht</td>
183
+ * <td>7</td>
184
+ * <td>ORCsy</td>
185
+ * </tr>
186
+ * <tr>
187
+ * <td>3</td>
188
+ * <td>Double Handed</td>
189
+ * <td>8</td>
190
+ * <td>DH International</td>
191
+ * </tr>
192
+ * <tr>
193
+ * <td>3</td>
194
+ * <td>Double Handed</td>
195
+ * <td>9</td>
196
+ * <td>DH Club</td>
197
+ * </tr>
198
+ * <tr>
199
+ * <td>4</td>
200
+ * <td>Multihull</td>
201
+ * <td>15</td>
202
+ * <td>Multihull International</td>
203
+ * </tr>
204
+ * <tr>
205
+ * <td>4</td>
206
+ * <td>Multihull</td>
207
+ * <td>16</td>
208
+ * <td>Multihull Club</td>
209
+ * </tr>
210
+ * <tr>
211
+ * <td>5</td>
212
+ * <td>Non Spinnaker</td>
213
+ * <td>10</td>
214
+ * <td>NS International</td>
215
+ * </tr>
216
+ * <tr>
217
+ * <td>5</td>
218
+ * <td>Non Spinnaker</td>
219
+ * <td>11</td>
220
+ * <td>NS Club</td>
221
+ * </tr>
222
+ * <tr>
223
+ * <td>6</td>
224
+ * <td>ORC Light</td>
225
+ * <td>13</td>
226
+ * <td>Light (Standard)</td>
227
+ * </tr>
228
+ * <tr>
229
+ * <td>6</td>
230
+ * <td>ORC Light</td>
231
+ * <td>14</td>
232
+ * <td>Light (Double Handed)</td>
233
+ * </tr>
234
+ * </table>
235
+ *
155 236
* @author Axel Uhl (D043530)
156 237
*/
157 238
public interface CountryOverview {