Skip to content

Commit

Permalink
Add BBK driver
Browse files Browse the repository at this point in the history
  • Loading branch information
charphi committed Jun 7, 2021
1 parent e4048bb commit ebeb690
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,31 +60,31 @@ protected IOSupplier<InputStream> calling(URL query, String mediaType) {
}

@Override
final public List<Dataflow> getFlows() throws IOException {
public List<Dataflow> getFlows() throws IOException {
URL url = getFlowsQuery();
return getFlows(url);
}

@Override
final public Dataflow getFlow(DataflowRef ref) throws IOException {
public Dataflow getFlow(DataflowRef ref) throws IOException {
URL url = getFlowQuery(ref);
return getFlow(url, ref);
}

@Override
final public DataStructure getStructure(DataStructureRef ref) throws IOException {
public DataStructure getStructure(DataStructureRef ref) throws IOException {
URL url = getStructureQuery(ref);
return getStructure(url, ref);
}

@Override
final public DataCursor getData(DataRequest request, DataStructure dsd) throws IOException {
public DataCursor getData(DataRequest request, DataStructure dsd) throws IOException {
URL url = getDataQuery(request);
return getData(dsd, url);
}

@Override
final public Duration ping() throws IOException {
public Duration ping() throws IOException {
Clock clock = Clock.systemDefaultZone();
Instant start = clock.instant();
getFlows();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package internal.sdmxdl.ri.web;

import internal.util.rest.RestQueryBuilder;
import lombok.AccessLevel;
import sdmxdl.*;

import java.net.URL;
Expand All @@ -9,6 +10,7 @@
import java.util.Map;

@lombok.Builder
@lombok.AllArgsConstructor(access = AccessLevel.PUBLIC)
public class Sdmx21RestQueries implements RiRestQueries {

private boolean trailingSlashRequired;
Expand All @@ -18,30 +20,31 @@ public class Sdmx21RestQueries implements RiRestQueries {

@Override
public RestQueryBuilder getFlowsQuery(URL endpoint) {
List<String> resource = getResource(SdmxResourceType.DATAFLOW);
return onMeta(endpoint, resource, FLOWS)
return onMeta(endpoint, SdmxResourceType.DATAFLOW, FLOWS)
.trailingSlash(trailingSlashRequired);
}

@Override
public RestQueryBuilder getFlowQuery(URL endpoint, DataflowRef ref) {
List<String> resource = getResource(SdmxResourceType.DATAFLOW);
return onMeta(endpoint, resource, ref)
return onMeta(endpoint, SdmxResourceType.DATAFLOW, ref)
.trailingSlash(trailingSlashRequired);
}

@Override
public RestQueryBuilder getStructureQuery(URL endpoint, DataStructureRef ref) {
List<String> resource = getResource(SdmxResourceType.DATASTRUCTURE);
return onMeta(endpoint, resource, ref)
return onMeta(endpoint, SdmxResourceType.DATASTRUCTURE, ref)
.param(REFERENCES_PARAM, "children")
.trailingSlash(trailingSlashRequired);
}

@Override
public RestQueryBuilder getDataQuery(URL endpoint, DataflowRef flowRef, Key key, DataFilter filter) {
List<String> resource = getResource(SdmxResourceType.DATA);
RestQueryBuilder result = onData(endpoint, resource, flowRef, key, DEFAULT_PROVIDER_REF);
RestQueryBuilder result = onData(endpoint, SdmxResourceType.DATA, flowRef, key, DEFAULT_PROVIDER_REF);
applyFilter(filter, result);
return result.trailingSlash(trailingSlashRequired);
}

protected void applyFilter(DataFilter filter, RestQueryBuilder result) {
switch (filter.getDetail()) {
case SERIES_KEYS_ONLY:
result.param(DETAIL_PARAM, "serieskeysonly");
Expand All @@ -53,10 +56,9 @@ public RestQueryBuilder getDataQuery(URL endpoint, DataflowRef flowRef, Key key,
result.param(DETAIL_PARAM, "nodata");
break;
}
return result.trailingSlash(trailingSlashRequired);
}

private List<String> getResource(SdmxResourceType type) {
protected List<String> getResource(SdmxResourceType type) {
List<String> result = customResources.get(type);
return result != null ? result : getDefaultResource(type);
}
Expand All @@ -74,19 +76,19 @@ private static List<String> getDefaultResource(SdmxResourceType type) {
}
}

private static RestQueryBuilder onMeta(URL endpoint, List<String> resource, ResourceRef<?> ref) {
protected RestQueryBuilder onMeta(URL endpoint, SdmxResourceType resource, ResourceRef<?> ref) {
return RestQueryBuilder
.of(endpoint)
.path(resource)
.path(getResource(resource))
.path(ref.getAgency())
.path(ref.getId())
.path(ref.getVersion());
}

private static RestQueryBuilder onData(URL endpoint, List<String> resource, DataflowRef flowRef, Key key, String providerRef) {
protected RestQueryBuilder onData(URL endpoint, SdmxResourceType resource, DataflowRef flowRef, Key key, String providerRef) {
return RestQueryBuilder
.of(endpoint)
.path(resource)
.path(getResource(resource))
.path(flowRef.toString())
.path(key.toString())
.path(providerRef);
Expand All @@ -98,8 +100,8 @@ private static RestQueryBuilder onData(URL endpoint, List<String> resource, Data

private static final String DEFAULT_PROVIDER_REF = "all";

private static final String REFERENCES_PARAM = "references";
private static final String DETAIL_PARAM = "detail";
protected static final String REFERENCES_PARAM = "references";
protected static final String DETAIL_PARAM = "detail";

private static final DataflowRef FLOWS = DataflowRef.of("all", "all", "latest");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Copyright 2017 National Bank of Belgium
*
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved
* by the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*/
package internal.sdmxdl.ri.web.drivers;

import internal.sdmxdl.ri.web.RestClients;
import internal.sdmxdl.ri.web.Sdmx21RestClient;
import internal.sdmxdl.ri.web.Sdmx21RestQueries;
import internal.sdmxdl.ri.web.SdmxResourceType;
import internal.util.rest.HttpRest;
import internal.util.rest.RestQueryBuilder;
import nbbrd.io.text.Parser;
import nbbrd.service.ServiceProvider;
import org.checkerframework.checker.nullness.qual.NonNull;
import sdmxdl.*;
import sdmxdl.ext.ObsFactory;
import sdmxdl.ext.ObsParser;
import sdmxdl.util.SdmxFix;
import sdmxdl.util.parser.DefaultObsParser;
import sdmxdl.util.parser.FreqFactory;
import sdmxdl.util.parser.PeriodParsers;
import sdmxdl.util.web.DataRequest;
import sdmxdl.util.web.SdmxWebClient;
import sdmxdl.util.web.SdmxWebDriverSupport;
import sdmxdl.web.SdmxWebSource;
import sdmxdl.web.spi.SdmxWebContext;
import sdmxdl.web.spi.SdmxWebDriver;
import sdmxdl.xml.stream.SdmxXmlStreams;

import java.io.IOException;
import java.net.URL;
import java.util.*;

import static sdmxdl.ext.SdmxMediaType.GENERIC_DATA_21;
import static sdmxdl.util.SdmxFix.Category.QUERY;

/**
* @author Philippe Charles
*/
@ServiceProvider(SdmxWebDriver.class)
public final class BbkDriver implements SdmxWebDriver {

private static final String RI_BBK = "ri:bbk";

@lombok.experimental.Delegate
private final SdmxWebDriverSupport support = SdmxWebDriverSupport
.builder()
.name(RI_BBK)
.rank(NATIVE_RANK)
.client(BbkDriver::of)
.supportedProperties(RestClients.CONNECTION_PROPERTIES)
.source(SdmxWebSource
.builder()
.name("BBK")
.description("Bundesbank")
.driver(RI_BBK)
.endpointOf("https://api.statistiken.bundesbank.de/rest")
.websiteOf("https://www.bundesbank.de/en/statistics/time-series-databases")
.monitorOf("UptimeRobot", "m788346159-bf4da8fbc0d9c633266a9387")
.build())
.build();

private static SdmxWebClient of(SdmxWebSource s, SdmxWebContext c) throws IOException {
return new BbkClient(
SdmxWebClient.getClientName(s),
s.getEndpoint(),
c.getLanguages(),
RestClients.getRestClient(s, c)
);
}

// FIXME: add support of multiple mediaTypes in HTTP requests
private static final class BbkClient extends Sdmx21RestClient {

private BbkClient(String name, URL endpoint, LanguagePriorityList langs, HttpRest.Client executor) {
super(name, endpoint, langs, executor, true, BbkQueries.INSTANCE, BbkObsFactory.INSTANCE);
}

@Override
public DataCursor getData(DataRequest request, DataStructure dsd) throws IOException {
if (request.getKey().equals(Key.ALL)) {
request = request.toBuilder().key(alternateAllOf(dsd)).build();
}
return super.getData(request, dsd);
}

@SdmxFix(id = 6, category = QUERY, cause = "Data key parameter does not support 'all' keyword")
private Key alternateAllOf(DataStructure dsd) {
return Key.of(new String[dsd.getDimensions().size()]);
}

@Override
protected DataCursor getData(DataStructure dsd, URL url) throws IOException {
return SdmxXmlStreams
.genericData21(dsd, dataFactory)
.parseStream(calling(url, GENERIC_DATA_21));
}
}

private static final class BbkQueries extends Sdmx21RestQueries {

private static final BbkQueries INSTANCE = new BbkQueries();

private BbkQueries() {
super(false, getCustomResources());
}

@SdmxFix(id = 1, category = QUERY, cause = "Meta uses custom resources path")
private static Map<SdmxResourceType, List<String>> getCustomResources() {
HashMap<SdmxResourceType, List<String>> result = new HashMap<>();
result.put(SdmxResourceType.DATAFLOW, Arrays.asList("metadata", "dataflow"));
result.put(SdmxResourceType.DATASTRUCTURE, Arrays.asList("metadata", "datastructure"));
return result;
}

@SdmxFix(id = 2, category = QUERY, cause = "Resource ref does not support 'all' in agencyID")
private static final String AGENCY_ID = "BBK";

@SdmxFix(id = 3, category = QUERY, cause = "Resource ref does not support 'all' in resourceID")
private static boolean isValid(ResourceRef<?> ref) {
return !ref.getId().equals("all");
}

@Override
protected RestQueryBuilder onMeta(URL endpoint, SdmxResourceType resource, ResourceRef<?> ref) {
RestQueryBuilder result = RestQueryBuilder
.of(endpoint)
.path(getResource(resource))
.path(AGENCY_ID);
if (isValid(ref)) {
result.path(ref.getId());
}
return result;
}

@SdmxFix(id = 4, category = QUERY, cause = "Data does not support providerRef")
@Override
protected RestQueryBuilder onData(URL endpoint, SdmxResourceType resource, DataflowRef flowRef, Key key, String providerRef) {
return RestQueryBuilder
.of(endpoint)
.path(getResource(resource))
.path(flowRef.getId())
.path(key.toString());
}

@SdmxFix(id = 5, category = QUERY, cause = "Data detail parameter for series-keys-only has a typo")
@Override
protected void applyFilter(DataFilter filter, RestQueryBuilder result) {
if (filter.getDetail().equals(DataFilter.Detail.SERIES_KEYS_ONLY)) {
result.param(DETAIL_PARAM, "serieskeyonly");
} else {
super.applyFilter(filter, result);
}
}
}

// FIXME: use TIME_FORMAT attribute instead of FREQ dimension in SDMX21 ?
private enum BbkObsFactory implements ObsFactory {
INSTANCE;

@Override
public @NonNull ObsParser getObsParser(@NonNull DataStructure dsd) {
Objects.requireNonNull(dsd);
return new DefaultObsParser(
FreqFactory.sdmx20(dsd),
PeriodParsers::onStandardFreq,
Parser.onDouble()
);
}
}
}
3 changes: 2 additions & 1 deletion sdmx-dl-provider-ri/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
internal.sdmxdl.ri.web.drivers.DotStatDriver2,
internal.sdmxdl.ri.web.drivers.FileDriver,
internal.sdmxdl.ri.web.drivers.NbbDriver2,
internal.sdmxdl.ri.web.drivers.Sdmx21Driver2;
internal.sdmxdl.ri.web.drivers.Sdmx21Driver2,
internal.sdmxdl.ri.web.drivers.BbkDriver;

provides sdmxdl.file.spi.SdmxFileReader with
internal.sdmxdl.ri.file.readers.XmlFileReader;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2018 National Bank of Belgium
*
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved
* by the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*/
package internal.sdmxdl.ri.web.drivers;

import org.junit.Test;
import sdmxdl.tck.web.SdmxWebDriverAssert;

/**
* @author Philippe Charles
*/
public class BbkDriverTest {

@Test
public void testCompliance() {
SdmxWebDriverAssert.assertCompliance(new BbkDriver());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
<data key="BANKRUPT_2.AUS.0.A" minSeries="1" minObs="3"/>
</flow>
</source>
<source name="BBK" minFlows="49">
<flow ref="BBEX3" dims="6">
<data key="M.ISK.EUR+USD.CA.AC.A01" minSeries="2" minObs="694"/>
</flow>
</source>
<source name="BIS" minFlows="19">
<flow ref="BISWEB_EERDATAFLOW" dims="4">
<data key="M.N.B.AE" minSeries="1" minObs="326"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* @author Philippe Charles
*/
@lombok.Value
@lombok.Builder(toBuilder = true)
public class DataRequest {

@lombok.NonNull
Expand Down

0 comments on commit ebeb690

Please sign in to comment.