DataProvidersManager.java
/* Copyright 2002-2020 CS Group
* Licensed to CS Group (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.data;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.gnss.HatanakaCompressFilter;
/** This class manages supported {@link DataProvider data providers}.
* <p>
* This class is the primary point of access for all data loading features. It
* is used for example to load Earth Orientation Parameters used by IERS frames,
* to load UTC leap seconds used by time scales, to load planetary ephemerides ...
*
* <p>
* It is user-customizable: users can add their own data providers at will. This
* allows them for example to use a database or an existing data loading library
* in order to embed an Orekit enabled application in a global system with its
* own data handling mechanisms. There is no upper limitation on the number of
* providers, but often each application will use only a few.
* </p>
*
* <p>
* If the list of providers is empty when attempting to {@link #feed(String, DataLoader)
* feed} a file loader, the {@link #addDefaultProviders()} method is called
* automatically to set up a default configuration. This default configuration
* contains one {@link DataProvider data provider} for each component of the
* path-like list specified by the java property <code>orekit.data.path</code>.
* See the {@link #feed(String, DataLoader) feed} method documentation for further
* details. The default providers configuration is <em>not</em> set up if the list
* is not empty. If users want to have both the default providers and additional
* providers, they must call explicitly the {@link #addDefaultProviders()} method.
* </p>
*
* <p>
* The default configuration uses a predefined set of {@link DataFilter data filters}
* that already handled gzip-compressed files (recognized by the {@code .gz} suffix)
* and Unix-compressed files (recognized by the {@code .Z} suffix).
* Users can {@link #addFilter(DataFilter) add} custom filters for handling specific
* types of filters (decompression, deciphering...).
* </p>
*
* @author Luc Maisonobe
* @see DirectoryCrawler
* @see ClasspathCrawler
*/
public class DataProvidersManager {
/** Name of the property defining the root directories or zip/jar files path for default configuration. */
public static final String OREKIT_DATA_PATH = "orekit.data.path";
/** Supported data providers. */
private final List<DataProvider> providers;
/** Supported filters.
* @since 9.2
*/
private final List<DataFilter> filters;
/** Number of predefined filters. */
private final int predefinedFilters;
/** Loaded data. */
private final Set<String> loaded;
/** Build an instance with default configuration. */
public DataProvidersManager() {
providers = new ArrayList<>();
filters = new ArrayList<>();
loaded = new LinkedHashSet<>();
// set up predefined filters
addFilter(new GzipFilter());
addFilter(new UnixCompressFilter());
addFilter(new HatanakaCompressFilter());
predefinedFilters = filters.size();
}
/**
* Get the default instance.
*
* @return default instance of the manager.
* @see DataContext
* @deprecated This class is no longer a singleton. In order to support loading
* multiple data sets code should be updated to accept an instance of this class. If
* you need to maintain compatibility with Orekit 10.0's behavior use the default data
* context: {@code DataContext.getDefault().getDataProvidersManager()}.
*/
@Deprecated
@DefaultDataContext
public static DataProvidersManager getInstance() {
return DataContext.getDefault().getDataProvidersManager();
}
/** Add the default providers configuration.
* <p>
* The default configuration contains one {@link DataProvider data provider}
* for each component of the path-like list specified by the java property
* <code>orekit.data.path</code>.
* </p>
* <p>
* If the property is not set or is null, no data will be available to the library
* (for example no pole corrections will be applied and only predefined UTC steps
* will be taken into account). No errors will be triggered in this case.
* </p>
* <p>
* If the property is set, it must contains a list of existing directories or zip/jar
* archives. One {@link DirectoryCrawler} instance will be set up for each
* directory and one {@link ZipJarCrawler} instance (configured to look for the
* archive in the filesystem) will be set up for each zip/jar archive. The list
* elements in the java property are separated using the standard path separator for
* the operating system as returned by {@link System#getProperty(String)
* System.getProperty("path.separator")}. This standard path separator is ":" on
* Linux and Unix type systems and ";" on Windows types systems.
* </p>
*/
public void addDefaultProviders() {
// get the path containing all components
final String path = System.getProperty(OREKIT_DATA_PATH);
if ((path != null) && !"".equals(path)) {
// extract the various components
for (final String name : path.split(System.getProperty("path.separator"))) {
if (!"".equals(name)) {
final File file = new File(name);
// check component
if (!file.exists()) {
if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, name);
} else {
throw new OrekitException(OrekitMessages.DATA_ROOT_DIRECTORY_DOES_NOT_EXIST, name);
}
}
if (file.isDirectory()) {
addProvider(new DirectoryCrawler(file));
} else if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
addProvider(new ZipJarCrawler(file));
} else {
throw new OrekitException(OrekitMessages.NEITHER_DIRECTORY_NOR_ZIP_OR_JAR, name);
}
}
}
}
}
/** Add a data provider to the supported list.
* @param provider data provider to add
* @see #removeProvider(DataProvider)
* @see #clearProviders()
* @see #isSupported(DataProvider)
* @see #getProviders()
*/
public void addProvider(final DataProvider provider) {
providers.add(provider);
}
/** Remove one provider.
* @param provider provider instance to remove
* @return instance removed (null if the provider was not already present)
* @see #addProvider(DataProvider)
* @see #clearProviders()
* @see #isSupported(DataProvider)
* @see #getProviders()
* @since 5.1
*/
public DataProvider removeProvider(final DataProvider provider) {
for (final Iterator<DataProvider> iterator = providers.iterator(); iterator.hasNext();) {
final DataProvider current = iterator.next();
if (current == provider) {
iterator.remove();
return provider;
}
}
return null;
}
/** Remove all data providers.
* @see #addProvider(DataProvider)
* @see #removeProvider(DataProvider)
* @see #isSupported(DataProvider)
* @see #getProviders()
*/
public void clearProviders() {
providers.clear();
}
/** Add a data filter.
* @param filter filter to add
* @see #applyAllFilters(NamedData)
* @see #clearFilters()
* @since 9.2
*/
public void addFilter(final DataFilter filter) {
filters.add(filter);
}
/** Remove all data filters, except the predefined ones.
* @see #addFilter(DataFilter)
* @since 9.2
*/
public void clearFilters() {
filters.subList(predefinedFilters, filters.size()).clear();
}
/** Apply all the relevant data filters, taking care of layers.
* <p>
* If several filters can be applied, they will all be applied
* as a stack, even recursively if required. This means that if
* filter A applies to files with names of the form base.ext.a
* and filter B applies to files with names of the form base.ext.b,
* then providing base.ext.a.b.a will result in filter A being
* applied on top of filter B which itself is applied on top of
* another instance of filter A.
* </p>
* @param original original named data
* @return fully filtered named data
* @exception IOException if some data stream cannot be filtered
* @see #addFilter(DataFilter)
* @see #clearFilters()
* @since 9.2
*/
public NamedData applyAllFilters(final NamedData original)
throws IOException {
NamedData top = original;
for (boolean filtering = true; filtering;) {
filtering = false;
for (final DataFilter filter : filters) {
final NamedData filtered = filter.filter(top);
if (filtered != top) {
// the filter has been applied, we need to restart the loop
top = filtered;
filtering = true;
break;
}
}
}
return top;
}
/** Check if some provider is supported.
* @param provider provider to check
* @return true if the specified provider instance is already in the supported list
* @see #addProvider(DataProvider)
* @see #removeProvider(DataProvider)
* @see #clearProviders()
* @see #getProviders()
* @since 5.1
*/
public boolean isSupported(final DataProvider provider) {
for (final DataProvider current : providers) {
if (current == provider) {
return true;
}
}
return false;
}
/** Get an unmodifiable view of the list of supported providers.
* @return unmodifiable view of the list of supported providers
* @see #addProvider(DataProvider)
* @see #removeProvider(DataProvider)
* @see #clearProviders()
* @see #isSupported(DataProvider)
*/
public List<DataProvider> getProviders() {
return Collections.unmodifiableList(providers);
}
/** Get an unmodifiable view of the set of data file names that have been loaded.
* <p>
* The names returned are exactly the ones that were given to the {@link
* DataLoader#loadData(InputStream, String) DataLoader.loadData} method.
* </p>
* @return unmodifiable view of the set of data file names that have been loaded
* @see #feed(String, DataLoader)
* @see #clearLoadedDataNames()
*/
public Set<String> getLoadedDataNames() {
return Collections.unmodifiableSet(loaded);
}
/** Clear the set of data file names that have been loaded.
* @see #getLoadedDataNames()
*/
public void clearLoadedDataNames() {
loaded.clear();
}
/** Feed a data file loader by browsing all data providers.
* <p>
* If this method is called with an empty list of providers, a default
* providers configuration is set up. This default configuration contains
* only one {@link DataProvider data provider}: a {@link DirectoryCrawler}
* instance that loads data from files located somewhere in a directory hierarchy.
* This default provider is <em>not</em> added if the list is not empty. If users
* want to have both the default provider and other providers, they must add it
* explicitly.
* </p>
* <p>
* The providers are used in the order in which they were {@link #addProvider(DataProvider)
* added}. As soon as one provider is able to feed the data loader, the loop is
* stopped. If no provider is able to feed the data loader, then the last error
* triggered is thrown.
* </p>
* @param supportedNames regular expression for file names supported by the visitor
* @param loader data loader to use
* @return true if some data has been loaded
*/
public boolean feed(final String supportedNames, final DataLoader loader) {
final Pattern supported = Pattern.compile(supportedNames);
// set up a default configuration if no providers have been set
if (providers.isEmpty()) {
addDefaultProviders();
}
// monitor the data that the loader will load
final DataLoader monitoredLoader = new MonitoringWrapper(loader);
// crawl the data collection
OrekitException delayedException = null;
for (final DataProvider provider : providers) {
try {
// try to feed the visitor using the current provider
if (provider.feed(supported, monitoredLoader, this)) {
return true;
}
} catch (OrekitException oe) {
// remember the last error encountered
delayedException = oe;
}
}
if (delayedException != null) {
throw delayedException;
}
return false;
}
/** Data loading monitoring wrapper class. */
private class MonitoringWrapper implements DataLoader {
/** Wrapped loader. */
private final DataLoader loader;
/** Simple constructor.
* @param loader loader to monitor
*/
MonitoringWrapper(final DataLoader loader) {
this.loader = loader;
}
/** {@inheritDoc} */
public boolean stillAcceptsData() {
// delegate to monitored loader
return loader.stillAcceptsData();
}
/** {@inheritDoc} */
public void loadData(final InputStream input, final String name)
throws IOException, ParseException, OrekitException {
// delegate to monitored loader
loader.loadData(input, name);
// monitor the fact new data has been loaded
loaded.add(name);
}
}
}