DataProvidersManager.java

  1. /* Copyright 2002-2019 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.data;

  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.text.ParseException;
  22. import java.util.ArrayList;
  23. import java.util.Collections;
  24. import java.util.Iterator;
  25. import java.util.LinkedHashSet;
  26. import java.util.List;
  27. import java.util.Set;
  28. import java.util.regex.Pattern;

  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;

  31. /** Singleton class managing all supported {@link DataProvider data providers}.

  32.  * <p>
  33.  * This class is the single point of access for all data loading features. It
  34.  * is used for example to load Earth Orientation Parameters used by IERS frames,
  35.  * to load UTC leap seconds used by time scales, to load planetary ephemerides ...
  36.  *
  37.  * <p>
  38.  * It is user-customizable: users can add their own data providers at will. This
  39.  * allows them for example to use a database or an existing data loading library
  40.  * in order to embed an Orekit enabled application in a global system with its
  41.  * own data handling mechanisms. There is no upper limitation on the number of
  42.  * providers, but often each application will use only a few.
  43.  * </p>
  44.  *
  45.  * <p>
  46.  * If the list of providers is empty when attempting to {@link #feed(String, DataLoader)
  47.  * feed} a file loader, the {@link #addDefaultProviders()} method is called
  48.  * automatically to set up a default configuration. This default configuration
  49.  * contains one {@link DataProvider data provider} for each component of the
  50.  * path-like list specified by the java property <code>orekit.data.path</code>.
  51.  * See the {@link #feed(String, DataLoader) feed} method documentation for further
  52.  * details. The default providers configuration is <em>not</em> set up if the list
  53.  * is not empty. If users want to have both the default providers and additional
  54.  * providers, they must call explicitly the {@link #addDefaultProviders()} method.
  55.  * </p>
  56.  *
  57.  * <p>
  58.  * The default configuration uses a predefined set of {@link DataFilter data filters}
  59.  * that already handled gzip-compressed files (recognized by the {@code .gz} suffix)
  60.  * and Unix-compressed files (recognized by the {@code .Z} suffix).
  61.  * Users can {@link #addFilter(DataFilter) add} custom filters for handling specific
  62.  * types of filters (decompression, deciphering...).
  63.  * </p>
  64.  *
  65.  * @author Luc Maisonobe
  66.  * @see DirectoryCrawler
  67.  * @see ClasspathCrawler
  68.  */
  69. public class DataProvidersManager {

  70.     /** Name of the property defining the root directories or zip/jar files path for default configuration. */
  71.     public static final String OREKIT_DATA_PATH = "orekit.data.path";

  72.     /** Supported data providers. */
  73.     private final List<DataProvider> providers;

  74.     /** Supported filters.
  75.      * @since 9.2
  76.      */
  77.     private final List<DataFilter> filters;

  78.     /** Number of predefined filters. */
  79.     private final int predefinedFilters;

  80.     /** Loaded data. */
  81.     private final Set<String> loaded;

  82.     /** Build an instance with default configuration.
  83.      * <p>
  84.      * This is a singleton, so the constructor is private.
  85.      * </p>
  86.      */
  87.     private DataProvidersManager() {
  88.         providers = new ArrayList<DataProvider>();
  89.         filters   = new ArrayList<>();
  90.         loaded    = new LinkedHashSet<String>();

  91.         // set up predefined filters
  92.         addFilter(new GzipFilter());
  93.         addFilter(new UnixCompressFilter());

  94.         predefinedFilters = filters.size();

  95.     }

  96.     /** Get the unique instance.
  97.      * @return unique instance of the manager.
  98.      */
  99.     public static DataProvidersManager getInstance() {
  100.         return LazyHolder.INSTANCE;
  101.     }

  102.     /** Add the default providers configuration.
  103.      * <p>
  104.      * The default configuration contains one {@link DataProvider data provider}
  105.      * for each component of the path-like list specified by the java property
  106.      * <code>orekit.data.path</code>.
  107.      * </p>
  108.      * <p>
  109.      * If the property is not set or is null, no data will be available to the library
  110.      * (for example no pole corrections will be applied and only predefined UTC steps
  111.      * will be taken into account). No errors will be triggered in this case.
  112.      * </p>
  113.      * <p>
  114.      * If the property is set, it must contains a list of existing directories or zip/jar
  115.      * archives. One {@link DirectoryCrawler} instance will be set up for each
  116.      * directory and one {@link ZipJarCrawler} instance (configured to look for the
  117.      * archive in the filesystem) will be set up for each zip/jar archive. The list
  118.      * elements in the java property are separated using the standard path separator for
  119.      * the operating system as returned by {@link System#getProperty(String)
  120.      * System.getProperty("path.separator")}. This standard path separator is ":" on
  121.      * Linux and Unix type systems and ";" on Windows types systems.
  122.      * </p>
  123.      */
  124.     public void addDefaultProviders() {

  125.         // get the path containing all components
  126.         final String path = System.getProperty(OREKIT_DATA_PATH);
  127.         if ((path != null) && !"".equals(path)) {

  128.             // extract the various components
  129.             for (final String name : path.split(System.getProperty("path.separator"))) {
  130.                 if (!"".equals(name)) {

  131.                     final File file = new File(name);

  132.                     // check component
  133.                     if (!file.exists()) {
  134.                         if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
  135.                             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, name);
  136.                         } else {
  137.                             throw new OrekitException(OrekitMessages.DATA_ROOT_DIRECTORY_DOES_NOT_EXIST, name);
  138.                         }
  139.                     }

  140.                     if (file.isDirectory()) {
  141.                         addProvider(new DirectoryCrawler(file));
  142.                     } else if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
  143.                         addProvider(new ZipJarCrawler(file));
  144.                     } else {
  145.                         throw new OrekitException(OrekitMessages.NEITHER_DIRECTORY_NOR_ZIP_OR_JAR, name);
  146.                     }

  147.                 }
  148.             }
  149.         }

  150.     }

  151.     /** Add a data provider to the supported list.
  152.      * @param provider data provider to add
  153.      * @see #removeProvider(DataProvider)
  154.      * @see #clearProviders()
  155.      * @see #isSupported(DataProvider)
  156.      * @see #getProviders()
  157.      */
  158.     public void addProvider(final DataProvider provider) {
  159.         providers.add(provider);
  160.     }

  161.     /** Remove one provider.
  162.      * @param provider provider instance to remove
  163.      * @return instance removed (null if the provider was not already present)
  164.      * @see #addProvider(DataProvider)
  165.      * @see #clearProviders()
  166.      * @see #isSupported(DataProvider)
  167.      * @see #getProviders()
  168.      * @since 5.1
  169.      */
  170.     public DataProvider removeProvider(final DataProvider provider) {
  171.         for (final Iterator<DataProvider> iterator = providers.iterator(); iterator.hasNext();) {
  172.             final DataProvider current = iterator.next();
  173.             if (current == provider) {
  174.                 iterator.remove();
  175.                 return provider;
  176.             }
  177.         }
  178.         return null;
  179.     }

  180.     /** Remove all data providers.
  181.      * @see #addProvider(DataProvider)
  182.      * @see #removeProvider(DataProvider)
  183.      * @see #isSupported(DataProvider)
  184.      * @see #getProviders()
  185.      */
  186.     public void clearProviders() {
  187.         providers.clear();
  188.     }

  189.     /** Add a data filter.
  190.      * @param filter filter to add
  191.      * @see #applyAllFilters(NamedData)
  192.      * @see #clearFilters()
  193.      * @since 9.2
  194.      */
  195.     public void addFilter(final DataFilter filter) {
  196.         filters.add(filter);
  197.     }

  198.     /** Remove all data filters, except the predefined ones.
  199.      * @see #addFilter(DataFilter)
  200.      * @since 9.2
  201.      */
  202.     public void clearFilters() {
  203.         for (int i = filters.size() - 1; i >= predefinedFilters; --i) {
  204.             filters.remove(i);
  205.         }
  206.     }

  207.     /** Apply all the relevant data filters, taking care of layers.
  208.      * <p>
  209.      * If several filters can be applied, they will all be applied
  210.      * as a stack, even recursively if required. This means that if
  211.      * filter A applies to files with names of the form base.ext.a
  212.      * and filter B applies to files with names of the form base.ext.b,
  213.      * then providing base.ext.a.b.a will result in filter A being
  214.      * applied on top of filter B which itself is applied on top of
  215.      * another instance of filter A.
  216.      * </p>
  217.      * @param original original named data
  218.      * @return fully filtered named data
  219.      * @exception IOException if some data stream cannot be filtered
  220.      * @see #addFilter(DataFilter)
  221.      * @see #clearFilters()
  222.      * @since 9.2
  223.      */
  224.     public NamedData applyAllFilters(final NamedData original)
  225.         throws IOException {
  226.         NamedData top = original;
  227.         for (boolean filtering = true; filtering;) {
  228.             filtering = false;
  229.             for (final DataFilter filter : filters) {
  230.                 final NamedData filtered = filter.filter(top);
  231.                 if (filtered != top) {
  232.                     // the filter has been applied, we need to restart the loop
  233.                     top       = filtered;
  234.                     filtering = true;
  235.                     break;
  236.                 }
  237.             }
  238.         }
  239.         return top;
  240.     }

  241.     /** Check if some provider is supported.
  242.      * @param provider provider to check
  243.      * @return true if the specified provider instance is already in the supported list
  244.      * @see #addProvider(DataProvider)
  245.      * @see #removeProvider(DataProvider)
  246.      * @see #clearProviders()
  247.      * @see #getProviders()
  248.      * @since 5.1
  249.      */
  250.     public boolean isSupported(final DataProvider provider) {
  251.         for (final DataProvider current : providers) {
  252.             if (current == provider) {
  253.                 return true;
  254.             }
  255.         }
  256.         return false;
  257.     }

  258.     /** Get an unmodifiable view of the list of supported providers.
  259.      * @return unmodifiable view of the list of supported providers
  260.      * @see #addProvider(DataProvider)
  261.      * @see #removeProvider(DataProvider)
  262.      * @see #clearProviders()
  263.      * @see #isSupported(DataProvider)
  264.      */
  265.     public List<DataProvider> getProviders() {
  266.         return Collections.unmodifiableList(providers);
  267.     }

  268.     /** Get an unmodifiable view of the set of data file names that have been loaded.
  269.      * <p>
  270.      * The names returned are exactly the ones that were given to the {@link
  271.      * DataLoader#loadData(InputStream, String) DataLoader.loadData} method.
  272.      * </p>
  273.      * @return unmodifiable view of the set of data file names that have been loaded
  274.      * @see #feed(String, DataLoader)
  275.      * @see #clearLoadedDataNames()
  276.      */
  277.     public Set<String> getLoadedDataNames() {
  278.         return Collections.unmodifiableSet(loaded);
  279.     }

  280.     /** Clear the set of data file names that have been loaded.
  281.      * @see #getLoadedDataNames()
  282.      */
  283.     public void clearLoadedDataNames() {
  284.         loaded.clear();
  285.     }

  286.     /** Feed a data file loader by browsing all data providers.
  287.      * <p>
  288.      * If this method is called with an empty list of providers, a default
  289.      * providers configuration is set up. This default configuration contains
  290.      * only one {@link DataProvider data provider}: a {@link DirectoryCrawler}
  291.      * instance that loads data from files located somewhere in a directory hierarchy.
  292.      * This default provider is <em>not</em> added if the list is not empty. If users
  293.      * want to have both the default provider and other providers, they must add it
  294.      * explicitly.
  295.      * </p>
  296.      * <p>
  297.      * The providers are used in the order in which they were {@link #addProvider(DataProvider)
  298.      * added}. As soon as one provider is able to feed the data loader, the loop is
  299.      * stopped. If no provider is able to feed the data loader, then the last error
  300.      * triggered is thrown.
  301.      * </p>
  302.      * @param supportedNames regular expression for file names supported by the visitor
  303.      * @param loader data loader to use
  304.      * @return true if some data has been loaded
  305.      */
  306.     public boolean feed(final String supportedNames, final DataLoader loader) {

  307.         final Pattern supported = Pattern.compile(supportedNames);

  308.         // set up a default configuration if no providers have been set
  309.         if (providers.isEmpty()) {
  310.             addDefaultProviders();
  311.         }

  312.         // monitor the data that the loader will load
  313.         final DataLoader monitoredLoader = new MonitoringWrapper(loader);

  314.         // crawl the data collection
  315.         OrekitException delayedException = null;
  316.         for (final DataProvider provider : providers) {
  317.             try {

  318.                 // try to feed the visitor using the current provider
  319.                 if (provider.feed(supported, monitoredLoader)) {
  320.                     return true;
  321.                 }

  322.             } catch (OrekitException oe) {
  323.                 // remember the last error encountered
  324.                 delayedException = oe;
  325.             }
  326.         }

  327.         if (delayedException != null) {
  328.             throw delayedException;
  329.         }

  330.         return false;

  331.     }

  332.     /** Data loading monitoring wrapper class. */
  333.     private class MonitoringWrapper implements DataLoader {

  334.         /** Wrapped loader. */
  335.         private final DataLoader loader;

  336.         /** Simple constructor.
  337.          * @param loader loader to monitor
  338.          */
  339.         MonitoringWrapper(final DataLoader loader) {
  340.             this.loader = loader;
  341.         }

  342.         /** {@inheritDoc} */
  343.         public boolean stillAcceptsData() {
  344.             // delegate to monitored loader
  345.             return loader.stillAcceptsData();
  346.         }

  347.         /** {@inheritDoc} */
  348.         public void loadData(final InputStream input, final String name)
  349.             throws IOException, ParseException, OrekitException {

  350.             // delegate to monitored loader
  351.             loader.loadData(input, name);

  352.             // monitor the fact new data has been loaded
  353.             loaded.add(name);

  354.         }

  355.     }

  356.     /** Holder for the manager singleton.
  357.      * <p>
  358.      * We use the Initialization on demand holder idiom to store
  359.      * the singletons, as it is both thread-safe, efficient (no
  360.      * synchronization) and works with all versions of java.
  361.      * </p>
  362.      */
  363.     private static class LazyHolder {

  364.         /** Unique instance. */
  365.         private static final DataProvidersManager INSTANCE = new DataProvidersManager();

  366.         /** Private constructor.
  367.          * <p>This class is a utility class, it should neither have a public
  368.          * nor a default constructor. This private constructor prevents
  369.          * the compiler from generating one automatically.</p>
  370.          */
  371.         private LazyHolder() {
  372.         }

  373.     }

  374. }