1   /* Copyright 2002-2020 CS GROUP
2    * Licensed to CS GROUP (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  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.text.ParseException;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Set;
29  import java.util.regex.Pattern;
30  
31  import org.orekit.annotation.DefaultDataContext;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitMessages;
34  import org.orekit.gnss.HatanakaCompressFilter;
35  
36  /** This class manages supported {@link DataProvider data providers}.
37   * <p>
38   * This class is the primary point of access for all data loading features. It
39   * is used for example to load Earth Orientation Parameters used by IERS frames,
40   * to load UTC leap seconds used by time scales, to load planetary ephemerides ...
41   *
42   * <p>
43   * It is user-customizable: users can add their own data providers at will. This
44   * allows them for example to use a database or an existing data loading library
45   * in order to embed an Orekit enabled application in a global system with its
46   * own data handling mechanisms. There is no upper limitation on the number of
47   * providers, but often each application will use only a few.
48   * </p>
49   *
50   * <p>
51   * If the list of providers is empty when attempting to {@link #feed(String, DataLoader)
52   * feed} a file loader, the {@link #addDefaultProviders()} method is called
53   * automatically to set up a default configuration. This default configuration
54   * contains one {@link DataProvider data provider} for each component of the
55   * path-like list specified by the java property <code>orekit.data.path</code>.
56   * See the {@link #feed(String, DataLoader) feed} method documentation for further
57   * details. The default providers configuration is <em>not</em> set up if the list
58   * is not empty. If users want to have both the default providers and additional
59   * providers, they must call explicitly the {@link #addDefaultProviders()} method.
60   * </p>
61   *
62   * <p>
63   * The default configuration uses a predefined set of {@link DataFilter data filters}
64   * that already handled gzip-compressed files (recognized by the {@code .gz} suffix)
65   * and Unix-compressed files (recognized by the {@code .Z} suffix).
66   * Users can {@link #addFilter(DataFilter) add} custom filters for handling specific
67   * types of filters (decompression, deciphering...).
68   * </p>
69   *
70   * @author Luc Maisonobe
71   * @see DirectoryCrawler
72   * @see ClasspathCrawler
73   */
74  public class DataProvidersManager {
75  
76      /** Name of the property defining the root directories or zip/jar files path for default configuration. */
77      public static final String OREKIT_DATA_PATH = "orekit.data.path";
78  
79      /** Supported data providers. */
80      private final List<DataProvider> providers;
81  
82      /** Supported filters.
83       * @since 9.2
84       */
85      private final List<DataFilter> filters;
86  
87      /** Number of predefined filters. */
88      private final int predefinedFilters;
89  
90      /** Loaded data. */
91      private final Set<String> loaded;
92  
93      /** Build an instance with default configuration. */
94      public DataProvidersManager() {
95          providers = new ArrayList<>();
96          filters   = new ArrayList<>();
97          loaded    = new LinkedHashSet<>();
98  
99          // set up predefined filters
100         addFilter(new GzipFilter());
101         addFilter(new UnixCompressFilter());
102         addFilter(new HatanakaCompressFilter());
103 
104         predefinedFilters = filters.size();
105 
106     }
107 
108     /**
109      * Get the default instance.
110      *
111      * @return default instance of the manager.
112      * @see DataContext
113      * @deprecated This class is no longer a singleton. In order to support loading
114      * multiple data sets code should be updated to accept an instance of this class. If
115      * you need to maintain compatibility with Orekit 10.0's behavior use the default data
116      * context: {@code DataContext.getDefault().getDataProvidersManager()}.
117      */
118     @Deprecated
119     @DefaultDataContext
120     public static DataProvidersManager getInstance() {
121         return DataContext.getDefault().getDataProvidersManager();
122     }
123 
124     /** Add the default providers configuration.
125      * <p>
126      * The default configuration contains one {@link DataProvider data provider}
127      * for each component of the path-like list specified by the java property
128      * <code>orekit.data.path</code>.
129      * </p>
130      * <p>
131      * If the property is not set or is null, no data will be available to the library
132      * (for example no pole corrections will be applied and only predefined UTC steps
133      * will be taken into account). No errors will be triggered in this case.
134      * </p>
135      * <p>
136      * If the property is set, it must contains a list of existing directories or zip/jar
137      * archives. One {@link DirectoryCrawler} instance will be set up for each
138      * directory and one {@link ZipJarCrawler} instance (configured to look for the
139      * archive in the filesystem) will be set up for each zip/jar archive. The list
140      * elements in the java property are separated using the standard path separator for
141      * the operating system as returned by {@link System#getProperty(String)
142      * System.getProperty("path.separator")}. This standard path separator is ":" on
143      * Linux and Unix type systems and ";" on Windows types systems.
144      * </p>
145      */
146     public void addDefaultProviders() {
147 
148         // get the path containing all components
149         final String path = System.getProperty(OREKIT_DATA_PATH);
150         if ((path != null) && !"".equals(path)) {
151 
152             // extract the various components
153             for (final String name : path.split(System.getProperty("path.separator"))) {
154                 if (!"".equals(name)) {
155 
156                     final File file = new File(name);
157 
158                     // check component
159                     if (!file.exists()) {
160                         if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
161                             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, name);
162                         } else {
163                             throw new OrekitException(OrekitMessages.DATA_ROOT_DIRECTORY_DOES_NOT_EXIST, name);
164                         }
165                     }
166 
167                     if (file.isDirectory()) {
168                         addProvider(new DirectoryCrawler(file));
169                     } else if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
170                         addProvider(new ZipJarCrawler(file));
171                     } else {
172                         throw new OrekitException(OrekitMessages.NEITHER_DIRECTORY_NOR_ZIP_OR_JAR, name);
173                     }
174 
175                 }
176             }
177         }
178 
179     }
180 
181     /** Add a data provider to the supported list.
182      * @param provider data provider to add
183      * @see #removeProvider(DataProvider)
184      * @see #clearProviders()
185      * @see #isSupported(DataProvider)
186      * @see #getProviders()
187      */
188     public void addProvider(final DataProvider provider) {
189         providers.add(provider);
190     }
191 
192     /** Remove one provider.
193      * @param provider provider instance to remove
194      * @return instance removed (null if the provider was not already present)
195      * @see #addProvider(DataProvider)
196      * @see #clearProviders()
197      * @see #isSupported(DataProvider)
198      * @see #getProviders()
199      * @since 5.1
200      */
201     public DataProviderr">DataProvider removeProvider(final DataProvider provider) {
202         for (final Iterator<DataProvider> iterator = providers.iterator(); iterator.hasNext();) {
203             final DataProvider current = iterator.next();
204             if (current == provider) {
205                 iterator.remove();
206                 return provider;
207             }
208         }
209         return null;
210     }
211 
212     /** Remove all data providers.
213      * @see #addProvider(DataProvider)
214      * @see #removeProvider(DataProvider)
215      * @see #isSupported(DataProvider)
216      * @see #getProviders()
217      */
218     public void clearProviders() {
219         providers.clear();
220     }
221 
222     /** Add a data filter.
223      * @param filter filter to add
224      * @see #applyAllFilters(NamedData)
225      * @see #clearFilters()
226      * @since 9.2
227      */
228     public void addFilter(final DataFilter filter) {
229         filters.add(filter);
230     }
231 
232     /** Remove all data filters, except the predefined ones.
233      * @see #addFilter(DataFilter)
234      * @since 9.2
235      */
236     public void clearFilters() {
237         filters.subList(predefinedFilters, filters.size()).clear();
238     }
239 
240     /** Apply all the relevant data filters, taking care of layers.
241      * <p>
242      * If several filters can be applied, they will all be applied
243      * as a stack, even recursively if required. This means that if
244      * filter A applies to files with names of the form base.ext.a
245      * and filter B applies to files with names of the form base.ext.b,
246      * then providing base.ext.a.b.a will result in filter A being
247      * applied on top of filter B which itself is applied on top of
248      * another instance of filter A.
249      * </p>
250      * @param original original named data
251      * @return fully filtered named data
252      * @exception IOException if some data stream cannot be filtered
253      * @see #addFilter(DataFilter)
254      * @see #clearFilters()
255      * @since 9.2
256      */
257     public NamedDataamedData applyAllFilters(final NamedData original)
258         throws IOException {
259         NamedData top = original;
260         for (boolean filtering = true; filtering;) {
261             filtering = false;
262             for (final DataFilter filter : filters) {
263                 final NamedData filtered = filter.filter(top);
264                 if (filtered != top) {
265                     // the filter has been applied, we need to restart the loop
266                     top       = filtered;
267                     filtering = true;
268                     break;
269                 }
270             }
271         }
272         return top;
273     }
274 
275     /** Check if some provider is supported.
276      * @param provider provider to check
277      * @return true if the specified provider instance is already in the supported list
278      * @see #addProvider(DataProvider)
279      * @see #removeProvider(DataProvider)
280      * @see #clearProviders()
281      * @see #getProviders()
282      * @since 5.1
283      */
284     public boolean isSupported(final DataProvider provider) {
285         for (final DataProvider current : providers) {
286             if (current == provider) {
287                 return true;
288             }
289         }
290         return false;
291     }
292 
293     /** Get an unmodifiable view of the list of supported providers.
294      * @return unmodifiable view of the list of supported providers
295      * @see #addProvider(DataProvider)
296      * @see #removeProvider(DataProvider)
297      * @see #clearProviders()
298      * @see #isSupported(DataProvider)
299      */
300     public List<DataProvider> getProviders() {
301         return Collections.unmodifiableList(providers);
302     }
303 
304     /** Get an unmodifiable view of the set of data file names that have been loaded.
305      * <p>
306      * The names returned are exactly the ones that were given to the {@link
307      * DataLoader#loadData(InputStream, String) DataLoader.loadData} method.
308      * </p>
309      * @return unmodifiable view of the set of data file names that have been loaded
310      * @see #feed(String, DataLoader)
311      * @see #clearLoadedDataNames()
312      */
313     public Set<String> getLoadedDataNames() {
314         return Collections.unmodifiableSet(loaded);
315     }
316 
317     /** Clear the set of data file names that have been loaded.
318      * @see #getLoadedDataNames()
319      */
320     public void clearLoadedDataNames() {
321         loaded.clear();
322     }
323 
324     /** Feed a data file loader by browsing all data providers.
325      * <p>
326      * If this method is called with an empty list of providers, a default
327      * providers configuration is set up. This default configuration contains
328      * only one {@link DataProvider data provider}: a {@link DirectoryCrawler}
329      * instance that loads data from files located somewhere in a directory hierarchy.
330      * This default provider is <em>not</em> added if the list is not empty. If users
331      * want to have both the default provider and other providers, they must add it
332      * explicitly.
333      * </p>
334      * <p>
335      * The providers are used in the order in which they were {@link #addProvider(DataProvider)
336      * added}. As soon as one provider is able to feed the data loader, the loop is
337      * stopped. If no provider is able to feed the data loader, then the last error
338      * triggered is thrown.
339      * </p>
340      * @param supportedNames regular expression for file names supported by the visitor
341      * @param loader data loader to use
342      * @return true if some data has been loaded
343      */
344     public boolean feed(final String supportedNames, final DataLoader loader) {
345 
346         final Pattern supported = Pattern.compile(supportedNames);
347 
348         // set up a default configuration if no providers have been set
349         if (providers.isEmpty()) {
350             addDefaultProviders();
351         }
352 
353         // monitor the data that the loader will load
354         final DataLoader monitoredLoader = new MonitoringWrapper(loader);
355 
356         // crawl the data collection
357         OrekitException delayedException = null;
358         for (final DataProvider provider : providers) {
359             try {
360 
361                 // try to feed the visitor using the current provider
362                 if (provider.feed(supported, monitoredLoader, this)) {
363                     return true;
364                 }
365 
366             } catch (OrekitException oe) {
367                 // remember the last error encountered
368                 delayedException = oe;
369             }
370         }
371 
372         if (delayedException != null) {
373             throw delayedException;
374         }
375 
376         return false;
377 
378     }
379 
380     /** Data loading monitoring wrapper class. */
381     private class MonitoringWrapper implements DataLoader {
382 
383         /** Wrapped loader. */
384         private final DataLoader loader;
385 
386         /** Simple constructor.
387          * @param loader loader to monitor
388          */
389         MonitoringWrapper(final DataLoader loader) {
390             this.loader = loader;
391         }
392 
393         /** {@inheritDoc} */
394         public boolean stillAcceptsData() {
395             // delegate to monitored loader
396             return loader.stillAcceptsData();
397         }
398 
399         /** {@inheritDoc} */
400         public void loadData(final InputStream input, final String name)
401             throws IOException, ParseException, OrekitException {
402 
403             // delegate to monitored loader
404             loader.loadData(input, name);
405 
406             // monitor the fact new data has been loaded
407             loaded.add(name);
408 
409         }
410 
411     }
412 
413 }