Foundations of sdmx-core¶
SDMX Core has some foundational classes and configurations which underpin the framework. These must be understood before using the library.
Reading and Writing Streams¶
Problem Statement¶
SDMX Core streams information where possible to avoid keeping information in memory; this is especially true for reading and writing data, where datasets can be extremely large.
SDMX Core does not rely on file extensions or user-supplied metadata to know what type of SDMX message it is reading (format, version, type); instead it peeks into the stream and routes it to the correct reader. This enables the programmer to remain agnostic to the underlying exchange format, as SDMX Core takes care of interpreting the format and building it into a common Java model.
In order for SDMX Core to reliably work in this way, there must be a guarantee that the InputStream can be reset to the start of the stream after peeking into the stream. This is not a guarantee in Java as some implementations of InputStream do not support the reset method.
In addition, as Streams are passed between managers and services, there is not always a clear owner of the stream, which is problematic when it comes to knowing when a stream can be closed. If a stream is not closed, this can cause issues with deletion of temporary files, as the stream holds a lock on the file. In some cases a manager may think it should close the stream, but in some corner cases, it should not.
Readable Data Location¶
The solution to the problem is the SDMX Core ReadableDataLocation interface, which can wrap InputStreams, files, URLs, and other sources of information.
The ReadableDataLocation is an interface which allows the client to request the Java InputStream, and on each request the ReadableDataLocation passes a new InputStream, thus guaranteeing the stream is set to the beginning and allows multiple processes to read from the same source without creating a conflict.
The ReadableDataLocation can provide information on the base format of the stream, i.e. XML, CSV, JSON. It is able to do this by reading the first x-bytes of the stream to determine what it is reading. This enables the stream to be routed at a high level to the correct reader.
The ReadableDataLocation can be a compressed file or set of files, e.g. a zip. In this instance it offers methods to split the ReadableDataLocation into one or more uncompressed ReadableDataLocation, and automatically close the parent.
ReadableDataLocation tracks any InputStream that has been requested through its API, and on ReadableDataLocation.close all resources are removed, any InputStreams created from the ReadableDataLocation are closed, ensuring that any clients that forget to close the stream are handled in one location.
ReadableDataLocation can be protected, meaning that a higher-level process can protect it from being closed by a lower-level process that would ordinarily close the ReadableDataLocation.
ReadableDataLocation handles large streams by holding the stream off heap, e.g. by creating a temporary file; the standard setup for SDMX Core is to use in-memory streams until a threshold is hit, and then overflow to the file system; see SdmxSourceReadableDataLocationFactory.
The ReadableDataLocationFactory should be used to create a ReadableDataLocation as it decouples the code from choosing which type of ReadableDataLocation is created, however the concrete types can be used directly if required.
The FusionSystem.getRdlFactory() can be used to obtain a ReadableDataLocationFactory.
Writeable Data Location¶
The WriteableDataLocation supports writing data to a stream, it provides an output stream on request. As the WriteableDataLocation is a ReadableDataLocation, once the output is written it can be passed into a service as a source of information.
The default WriteableDataLocation handles streaming by writing to memory and overflowing to the file system once the memory buffer is full; the memory buffer size is global, which prevents multiple small buffers from exhausting memory.
The WriteableDataLocationFactory should be used to create a WriteableDataLocation as it decouples the code from choosing which type of WriteableDataLocation is created, however the concrete types can be used directly if required.
The FusionSystem.getWdlFactory() can be used to obtain a WriteableDataLocationFactory.
Inversion of Control¶
SDMX Core uses the Inversion of Control (IOC) design principle where the control of object creation, configuration, and flow is transferred from application code to a framework or container.
The IOC can be provided by the Spring Framework, or using the SDMX Core framework, or a combination of both.
SingletonStore¶
SDMX Core introduced a SingletonStore class to decouple the framework from Spring, whilst maintaining the ability to use the Spring framework if required.
The SingletonStore provides access to Singletons in the system, and the ability to register Singleton instances, typically as part of the application configuration process.
Register a Singleton
A singleton is registered by calling the registerInstance method. The instance is registered in the SingletonStore.
Retrieve a Singleton
A singleton can be retrieved from its type, or any Interface / supertype.
ReadableDataLocationFactory rdlFactory = SingletonStore.getSingleton(ReadableDataLocationFactory.class);
In some cases the singleton may not be registered yet, but is expected to be registered in the future; this can be the case when a singleton is constructed as part of the application startup process. In these cases the manager can register its interest in the class, and is provided the instance as soon as it is registered.
SingletonStore.registerInterest(StructureReaderManager.class, (srm) -> {
this.structureParsingManager = srm;
});
The SingletonStore stores instances in its local maps, however an additional source of singletons can be provided by setting a ISingletonContainer on the SingletonStore. This enables the SingletonStore to search both its local maps, followed by the ISingletonContainer if the local maps did not contain the requested singleton.
@Override
public void contextInitialized(ServletContextEvent event) {
try {
super.contextInitialized(event);
ApplicationContext appContext = getCurrentWebApplicationContext();
//Register spring bean container with the
//SingletonStore and FusionBeanStore
container = new SpringBeansContainer(appContext);
....
}
}
FusionBeanStore¶
The FusionBeanStore is very similar to the SingletonStore in its ability to register and retrieve stateless instances from a global application container; the only difference is the FusionBeanStore can store multiple instances of the same interface — for example, a StructureReaderEngine can have multiple implementations, and each one will be stored in the FusionBeanStore. As the FusionBeanStore can store multiple instances of the same interface, a request to retrieve implementations of an interface returns a set of instances, not a single instance.
Note, the SingletonStore and FusionBeanStore are independent stores that serve different roles: the SingletonStore holds unique singleton instances, while the FusionBeanStore can hold multiple implementations of the same interface.
Register an Instance
A new instance is registered by calling registerInstance. The implementation class type can only be registered once, however different implementations of the same interface can be registered. When an instance is registered it is stored against its implementation type, and any supertypes or interfaces it extends or implements.
Retrieve a Singleton
A collection of any instances that are of a given type can be returned by passing the class type in. This enables instances to be retrieved at the level of the interface they implement, rather than requesting the concrete implementation class.
Collection<StructureReaderFactory> stuctureReaderFactories = FusionBeanStore.getBeans(StructureReaderFactory.class);
SDMX Core & the Spring Framework¶
Both the SingletonStore and the FusionBeanStore have an optional sub-container, typed as ISingletonContainer and IFusionContainer respectively. The SpringBeansContainer implements both ISingletonContainer and IFusionContainer and on construction of the SpringBeansContainer it self registers on both the SingletonStore and FusionBeanStore.
The following example shows how the spring framework can be integrated seamlessly into the SDMX Core libraries.
@SpringBootApplication
public class MySpringBootApplication {
@Autowired
private ApplicationContext ctx;
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
@Bean
ApplicationRunner startupInitialisation() {
return args ->
//Set the spring container on the SingletonStore
SingletonStore.setContainer(new SpringBeansContainer(ctx));
//Register some additional instances
SdmxJsonModule.register();
}
}
}
Plugin Framework¶
SdmxCore is a plugin framework, enabling managers to dynamically build the capabilities based on the registered engines and factories. A concrete example is reading data; the DataReaderManager is provided a stream of data in a ReadableDataLocation, it is responsible for finding an appropriate implementation of the DataReaderEngine interface based on the format of the dataset. The DataReaderManager does not know about dataset formats, instead it delegates the task to all the registered instances of the DataReaderFactory interface that are in the FusionBeanStore.
@Override
public Collection<DataReaderFactory> getDataReaderFactories() {
if(factories == null) {
synchronized(this) {
factories = new TreeSet<>(new FactoryComparator());
factories.addAll(FusionBeanStore.getBeans(DataReaderFactory.class));
}
}
return factories;
}
Each DataReaderFactory is asked in turn to analyse the stream of data, and determine if the factory is able to read the data, if it can it returns a DataReaderEngine, if it can't it returns null.
for(DataReaderFactory currentFactory : getDataReaderFactories()) {
DataReaderEngine dre = currentFactory.getDataReaderEngine(sourceData, ...);
if(dre != null) {
return dre;
}
}
The plugin paradigm enables high-level manager classes to delegate work to any registered implementation of a specific interface. This is a commonly used design pattern in SDMX Core, and is used for managers that read and write data, structures, and reference metadata.
Fusion Modules¶
Fusion Modules are provided as a convenience mechanism for quickly setting up a system, by registering stateless instances to the SingletonStore and FusionBeanStore. Modules are called in the global configuration of an application.
All modules implement the IFusionModule marker interface, which enables modules to be easily discovered. The implementations of the IFusionModule interface are designed to set up a specific capability in the system, such as the ability to read and write SDMX content in JSON format.
Implementations of the IFusionModule are found in the java module's application package.
The following modules exist in SDMX Core.
| Java Module | FusionModule | Description |
|---|---|---|
| fusion-core-sdmx | CoreSDMXModule | Register core singletons for SDMX work, includes registration of the DeferredModule, and SemverModule |
| fusion-core-sdmx | DeferredModule | ?? |
| fusion-core-sdmx | SemverModule | ??? |
| fusion-core-data | FusionCoreDataModule | is this required? |
| fusion-sdmx-json | FusionJsonModule | ??? |
| fusion-sdmx-json | SdmxJsonModule | ??? |
| fusion-sdmx-ml | SdmxMLModule | Register readers and writers for SDMX-ML (XML) |
| fusion-sdmx-csv | SdmxCsvModule | Register readers and writers for SDMX-CSV |
| fusion-sdmx-edi | SdmxEDIModule | Register readers and writers for SDMX-EDI |
| fusion-sdmx-publicationtable | PublicationTableModule | Register readers and writers for Publication Tables |
| fusion-sdmx-reporttemplate | ReportingTemplateModule | Register readers and writers for Report Templates |
| fusion-xl | FusionXLModule | Register readers and writers for FusionXL (excel plugin) |
Example usage of a module to provide support for reading and writing SDMX-JSON.
/**
* read JSON structure file and print URN of all maintainable structures
*/
public static void main(String[] args) {
//Support JSON reading and writing
SdmxJsonModule.register();
String file = args[0];
//The reader manager will automatically plugin the JSON
//reader factory, required for reading SDMX-JSON
StructureReaderManager srm = new StructureReaderManagerImpl();
//Create a ReadableDataLocation on the file, autoclose
try(ReadableDataLocation rdl = new ReadableDataLocationTmp(file)) {
//Parse SDMX-JSON file into SdmxBeans container
SdmxBeans beans = srm.parseStructures(rdl);
//loop each maintainable structure and print URN
beans.getAllMaintainables().forEach(m-> {
System.out.println(m.getUrn());
});
}
}
ApplicationStartupAware and Cache¶
The SDMX Core framework supports application startup aware classes through the interface ApplicationStartupAware and ApplicationStarter.
Any class which implements ApplicationStartupAware which is registered in the SingletonContainer or FusionBeanContainer (either directly or via Spring) will have the applicationStarted method called when the application has started. In order to make use of this feature an ApplicationStarter implementation must be used, as this is responsible for calling the applicationStarted methods on all ApplicationStartupAware implementations.
The SDMX Core framework provides an implementation of the ApplicationStartupAware interface. The ApplicationStartupAware implementation also calls the clearCache method of any registered class which implements the Cache interface; this is a useful feature if the application has a concept of restarting or resetting itself without the need to restart the JVM.
Exceptions¶
The SDMX Core framework has a common Exception class SdmxException all specific exception types extend this base class, for example SdmxNoResultsException. Each exception type contains a SDMX_ERROR_CODE which provides the SDMX exception Code expected in the response message.
Fusion System¶
FusionSystem provides static access to a global ReadableDataLocationFactory and WriteableDataLocationFactory, it also configures the system to use a MessageDecoder for decoding error codes into error messages from a properties file.
To use FusionSystem in an application call: FusionSystem.register();