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 }