direct search for valid ORC certificates using dedicated org.org API

This commit is contained in:
Axel Uhl committed 2021-01-08 23:05:01 +01:00
1 parent 9ec4525b15
commit 5aad5e596b
5 files changed
+225 -22

No files matched your search

@@ -1,11 +1,12 @@
package com.sap.sailing.domain.orc;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.StreamSupport;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -13,9 +14,8 @@ import org.junit.runners.model.Statement;
import com.sap.sailing.domain.common.orc.ORCCertificate;
import com.sap.sailing.domain.orc.ORCPublicCertificateDatabase.CertificateHandle;
import com.sap.sailing.domain.orc.ORCPublicCertificateDatabase.CountryOverview;
import com.sap.sailing.domain.orc.impl.ORCPublicCertificateDatabaseImpl;
import com.sap.sse.common.CountryCode;
import com.sap.sse.common.CountryCodeFactory;
/***
* A IgnoreInvalidOrcCerticatesRule is an implementation of TestRule. This class execution depends on
@@ -57,7 +57,7 @@ public class FailIfNoValidOrcCertificateRule implements TestRule {
this.description = description;
availableCerts = new ArrayList<>();
}
/***
* This method executes for every test case having {@link TestRule} annotation of
* {@link FailIfNoValidOrcCertificateRule} class. Assume statement at the end of this method evaluates whether
@@ -68,22 +68,21 @@ public class FailIfNoValidOrcCertificateRule implements TestRule {
boolean certificateExists = true;
FailIfNoValidOrcCertificates annotation = description.getAnnotation(FailIfNoValidOrcCertificates.class);
if (annotation != null) {
int currentYear = LocalDate.now().getYear();
final CountryOverview countryWithMostValidCertificates = StreamSupport
.stream(db.getCountriesWithValidCertificates().spliterator(), /* parallel */ false)
.max((c1, c2) -> c1.getCertCount() - c2.getCertCount()).get();
certificateExists = false;
for (int year=currentYear; year>=currentYear-1; year--) {
for (CountryCode countryCode : CountryCodeFactory.INSTANCE.getAll()) {
try {
Iterable<CertificateHandle> certificateHandles = db.search(countryCode, year, null, null, null, null, /* includeInvalid */ false);
Iterable<ORCCertificate> orcCertificates = db.getCertificates(certificateHandles);
orcCertificates.forEach(availableCerts::add);
if (orcCertificates.iterator().hasNext()) {
certificateExists = true;
break; // Note: this stops after the first valid certificates for a country have been read!
}
} catch (Exception ex) {
// Exceptions are ignored because we are searching for any country's ORC certificate availability.
}
try {
Iterable<CertificateHandle> certificateHandles = db.search(countryWithMostValidCertificates.getIssuingCountry(),
countryWithMostValidCertificates.getVPPYear(), null, null, null, null, /* includeInvalid */ false);
Iterable<ORCCertificate> orcCertificates = db.getCertificates(certificateHandles);
orcCertificates.forEach(availableCerts::add);
if (orcCertificates.iterator().hasNext()) {
certificateExists = true;
}
} catch (Exception ex) {
logger.log(Level.WARNING, "Problem trying to fetch certificates for country "+countryWithMostValidCertificates.getIssuingCountry()+
" and year "+countryWithMostValidCertificates.getVPPYear());
}
}
if (certificateExists) {
@@ -10,6 +10,9 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.StreamSupport;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.math.ArgumentOutsideDomainException;
import org.apache.commons.math.FunctionEvaluationException;
@@ -20,6 +23,8 @@ import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.w3c.dom.DOMException;
import org.xml.sax.SAXException;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.NauticalMileDistance;
@@ -31,7 +36,9 @@ import com.sap.sailing.domain.common.orc.impl.ORCPerformanceCurveCourseImpl;
import com.sap.sailing.domain.common.orc.impl.ORCPerformanceCurveLegImpl;
import com.sap.sailing.domain.orc.impl.ORCCertificatesJsonImporter;
import com.sap.sailing.domain.orc.impl.ORCPerformanceCurveImpl;
import com.sap.sailing.domain.orc.impl.ORCPublicCertificateDatabaseImpl;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.CountryCode;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
@@ -81,7 +88,8 @@ public class TestORCPerformanceCurve {
}
@BeforeClass
public static void initialize() throws IOException, ParseException {
public static void initialize() throws IOException, ParseException, DOMException, SAXException,
ParserConfigurationException, java.text.ParseException {
List<ORCPerformanceCurveLeg> legs = new ArrayList<>();
legs.add(new ORCPerformanceCurveLegImpl(new NauticalMileDistance(2.23), new DegreeBearingImpl(10)));
legs.add(new ORCPerformanceCurveLegImpl(new NauticalMileDistance(2.00), new DegreeBearingImpl(170)));
@@ -97,7 +105,12 @@ public class TestORCPerformanceCurve {
File fileWithSpecificBins = new File(RESOURCES + "newFormatCertificate.json");
importerWithSpecificBins = new ORCCertificatesJsonImporter().read(new FileInputStream(fileWithSpecificBins));
// Online File:
importerOnline = new ORCCertificatesJsonImporter().read(new URL("https://data.orc.org/public/WPub.dll?action=DownBoatRMS&CountryId=GER&ext=json").openStream());
final CountryCode countryWithMostValidCertificates = StreamSupport
.stream(ORCPublicCertificateDatabaseImpl.INSTANCE.getCountriesWithValidCertificates().spliterator(),
/* parallel */ false)
.max((c1, c2) -> c1.getCertCount() - c2.getCertCount()).get().getIssuingCountry();
importerOnline = new ORCCertificatesJsonImporter().read(new URL("https://data.orc.org/public/WPub.dll?action=DownBoatRMS&CountryId="+
countryWithMostValidCertificates.getThreeLetterIOCCode()+"&ext=json").openStream());
}
@Test
@@ -160,6 +160,14 @@ public class TestORCPublicCertificateDatabase {
* boat class name; could be set to cert.getBoatClassName() but there are
* deviations in ORC DBs and query API, so leaving null:
*/ null, /* includeInvalid */ false);
if (Util.isEmpty(certHandles)) {
// there were certs; get one from the previous year
certHandles = db.search(null, LocalDate.now().getYear()-1, null, cert.getBoatName(),
cert.getSailNumber(), /*
* boat class name; could be set to cert.getBoatClassName() but there are
* deviations in ORC DBs and query API, so leaving null:
*/ null, /* includeInvalid */ false);
}
Optional<CertificateHandle> certificateHandle = Optional.ofNullable(certHandles.iterator().hasNext() ? certHandles.iterator().next() : null);
assertTrue("No certificate found for handle "+certificateHandle+
" extracted from certificates "+certificates, certificateHandle.isPresent());
@@ -188,7 +196,7 @@ public class TestORCPublicCertificateDatabase {
new BoatClassImpl(orcCertificate.getBoatClassName(), true)));
}
for (Future<Set<ORCCertificate>> futureResult : futures) {
isYearFound = isYearFound || assertFoundYear(futureResult.get(), year);
isYearFound = isYearFound || assertFoundYear(futureResult.get(), year) || assertFoundYear(futureResult.get(), year-1);
}
assertTrue(isYearFound);
}
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.orc;
import java.io.IOException;
import java.text.ParseException;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Date;
@@ -9,6 +11,11 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Future;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMException;
import org.xml.sax.SAXException;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.orc.ORCCertificate;
import com.sap.sailing.domain.orc.impl.ORCPublicCertificateDatabaseImpl;
@@ -128,6 +135,36 @@ public interface ORCPublicCertificateDatabase {
return familyById.get(familyId);
}
}
/**
* Data about valid certificates in a country, as obtained, e.g., from http://data.orc.org/public/WPub.dll/RMS. Such a
* record, in its original XML representation, looks like this:
*
* <pre>
&lt;CountryId&gt;AUS&lt;/CountryId&gt;
&lt;Family&gt;&lt;/Family&gt;
&lt;CertType&gt;3&lt;/CertType&gt;
&lt;VPPYear&gt;2020&lt;/VPPYear&gt;
&lt;CertCount&gt;161&lt;/CertCount&gt;
&lt;CountryName&gt;Australia&lt;/CountryName&gt;
&lt;LastUpdate&gt;2021-01-08T08:12:57.000Z&lt;/LastUpdate&gt;
&lt;CertName&gt;Club&lt;/CertName&gt;
&lt;RMSCode&gt;CLUB&lt;/RMSCode&gt;
* </pre>
*
* @author Axel Uhl (D043530)
*/
public interface CountryOverview {
CountryCode getIssuingCountry();
CertificateFamily getFamily();
Integer getCertType();
Integer getVPPYear();
int getCertCount();
TimePoint getLastUpdate();
String getCertName();
String getRMSCode();
}
/**
* Equality and hash code of such handles is based on the {@link #getReferenceNumber() reference number} only.
*
@@ -229,4 +266,7 @@ public interface ORCPublicCertificateDatabase {
Future<Set<ORCCertificate>> search(String yachtName, String sailNumber, BoatClass boatClass);
Date parseDate(final String dateString) throws DateTimeParseException;
Iterable<CountryOverview> getCountriesWithValidCertificates()
throws SAXException, IOException, ParserConfigurationException, DOMException, ParseException;
}
@@ -2,7 +2,9 @@ package com.sap.sailing.domain.orc.impl;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
@@ -25,12 +27,14 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.HttpMessage;
import org.apache.http.HttpResponse;
@@ -46,6 +50,7 @@ import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.BoatClassMasterdata;
@@ -76,6 +81,7 @@ public class ORCPublicCertificateDatabaseImpl implements ORCPublicCertificateDat
private static final NameValuePair ACTION_PARAM = new BasicNameValuePair(ACTION_PARAM_NAME, ACTION_PARAM_VALUE_LIST_CERT);
private static final NameValuePair XSLP_PARAM = new BasicNameValuePair(XSLP_PARAM_NAME, XSLP_PARAM_VALUE_LIST_CERT);
private static final String SEARCH_URL = "http://data.orc.org/public/WPub.dll";
private static final String COUNTRY_OVERVIEW_URL = SEARCH_URL+"/RMS";
private static final String SINGLE_CERTIFICATE_DOWNLOAD_URL = "http://data.orc.org/public/WPub.dll?action=DownBoatRMS";
private static final String ROOT_ELEMENT = "ROOT";
private static final String DATA_ELEMENT = "DATA";
@@ -96,6 +102,62 @@ public class ORCPublicCertificateDatabaseImpl implements ORCPublicCertificateDat
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
};
private static class CountryOverviewImpl implements CountryOverview {
private final CountryCode issuingCountry;
private final CertificateFamily family;
private final Integer certType;
private final Integer vppYear;
private final int certCount;
private final TimePoint lastUpdate;
private final String certName;
private final String rmsCode;
protected CountryOverviewImpl(CountryCode issuingCountry, CertificateFamily family, Integer certType,
Integer vppYear, int certCount, TimePoint lastUpdate, String certName, String rmsCode) {
super();
this.issuingCountry = issuingCountry;
this.family = family;
this.certType = certType;
this.vppYear = vppYear;
this.certCount = certCount;
this.lastUpdate = lastUpdate;
this.certName = certName;
this.rmsCode = rmsCode;
}
@Override
public CountryCode getIssuingCountry() {
return issuingCountry;
}
@Override
public CertificateFamily getFamily() {
return family;
}
@Override
public Integer getCertType() {
return certType;
}
@Override
public Integer getVPPYear() {
return vppYear;
}
@Override
public int getCertCount() {
return certCount;
}
@Override
public TimePoint getLastUpdate() {
return lastUpdate;
}
@Override
public String getCertName() {
return certName;
}
@Override
public String getRMSCode() {
return rmsCode;
}
}
/**
* Hash code and equality as based on {@link #getReferenceNumber() the reference number field} only.
*
@@ -325,6 +387,81 @@ public class ORCPublicCertificateDatabaseImpl implements ORCPublicCertificateDat
return result;
}
@Override
public Iterable<CountryOverview> getCountriesWithValidCertificates()
throws SAXException, IOException, ParserConfigurationException, DOMException, ParseException {
final Set<ORCPublicCertificateDatabase.CountryOverview> result = new HashSet<>();
final HttpClient client = HttpClientBuilder.create().build();
final List<NameValuePair> params = new ArrayList<>();
final HttpPost postRequest = new HttpPost(COUNTRY_OVERVIEW_URL);
addAuthorizationHeader(postRequest);
logger.fine(()->"Searching for "+params+"...");
final HttpResponse processorResponse = client.execute(postRequest);
final InputStream content = processorResponse.getEntity().getContent();
final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
final Document document = builder.parse(content);
final NodeList dataNodes = document.getElementsByTagName(ROOT_ELEMENT).item(0).getChildNodes();
for (int dataNodeIndex=0; dataNodeIndex<dataNodes.getLength(); dataNodeIndex++) {
final Node dataNode = dataNodes.item(dataNodeIndex);
if (dataNode.getNodeName().equals(DATA_ELEMENT)) {
final NodeList rowNodes = dataNode.getChildNodes();
for (int rowNodeIndex=0; rowNodeIndex<rowNodes.getLength(); rowNodeIndex++) {
final Node rowNode = rowNodes.item(rowNodeIndex);
if (rowNode.getNodeName().equals(ROW_ELEMENT)) {
final CountryOverview countryOverview = parseCountryOverview(rowNode);
if (countryOverview.getVPPYear() != null) {
result.add(countryOverview);
}
}
}
}
}
logger.fine(()->"Search for "+params+" returned "+result.size()+" results");
return result;
}
private CountryOverview parseCountryOverview(Node rowNode) throws DOMException, ParseException {
final DateFormat isoDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.mmmX");
CountryCode issuingCountry = null;
CertificateFamily family = null;
Integer certType = null;
Integer vppYear = null;
int certCount = 0;
TimePoint lastUpdate = null;
String certName = null;
String rmsCode = null;
for (int i=0; i<rowNode.getChildNodes().getLength(); i++) {
final Node child = rowNode.getChildNodes().item(i);
switch (child.getNodeName()) {
case "CountryId":
issuingCountry = CountryCodeFactory.INSTANCE.getFromThreeLetterIOCName(child.getTextContent().trim());
break;
case "Family":
family = Util.hasLength(child.getTextContent().trim()) ? CertificateFamily.fromId(new Integer(child.getTextContent().trim())) : null;
break;
case "CertType":
certType = child.getTextContent().trim().isEmpty() ? null : Integer.valueOf(child.getTextContent().trim());
break;
case "VPPYear":
vppYear = child.getTextContent().trim().isEmpty() ? null : Integer.valueOf(child.getTextContent().trim());
break;
case "CertCount":
certCount = Integer.valueOf(child.getTextContent().trim()).intValue();
break;
case "LastUpdate":
lastUpdate = TimePoint.of(isoDateFormat.parse(child.getTextContent().trim()));
break;
case "CertName":
certName = child.getTextContent().trim();
break;
case "RMSCode":
rmsCode = child.getTextContent().trim();
break;
}
}
return new CountryOverviewImpl(issuingCountry, family, certType, vppYear, certCount, lastUpdate, certName, rmsCode);
}
private void addAuthorizationHeader(final HttpMessage httpMessage) {
httpMessage.addHeader("Authorization", "Basic "+new String(Base64.getEncoder().encode((USER_NAME+":"+getDecodedCredentials()).getBytes())));
}
@@ -486,7 +623,13 @@ public class ORCPublicCertificateDatabaseImpl implements ORCPublicCertificateDat
public ORCCertificate getCertificate(String referenceNumber, CertificateFamily family) throws Exception {
final String queryParameters = "ext=json&RefNo="+referenceNumber+"&family="+family.getFamilyQueryParamValue();
logger.fine("Obtaining certificate for reference number "+referenceNumber);
final ORCCertificate result = getSingleCertificate(queryParameters);
ORCCertificate result;
try {
result = getSingleCertificate(queryParameters);
} catch (Exception e) {
logger.log(Level.WARNING, "Error trying to find ORC certificate with reference number "+referenceNumber, e);
result = null;
}
if (result == null) {
logger.info("Couldn't find ORC certificate with reference number "+referenceNumber);
}