1 /* Copyright 2002-2025 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.errors.OrekitException;
32 import org.orekit.errors.OrekitMessages;
33 import org.orekit.files.rinex.HatanakaCompressFilter;
34
35 /** This class manages supported {@link DataProvider data providers}.
36 * <p>
37 * This class is the primary point of access for all data loading features. It
38 * is used for example to load Earth Orientation Parameters used by IERS frames,
39 * to load UTC leap seconds used by time scales, to load planetary ephemerides...
40 *
41 * <p>
42 * It is user-customizable: users can add their own data providers at will. This
43 * allows them for example to use a database or an existing data loading library
44 * in order to embed an Orekit enabled application in a global system with its
45 * own data handling mechanisms. There is no upper limitation on the number of
46 * providers, but often each application will use only a few.
47 * </p>
48 *
49 * <p>
50 * If the list of providers is empty when attempting to {@link #feed(String, DataLoader)
51 * feed} a file loader, the {@link #addDefaultProviders()} method is called
52 * automatically to set up a default configuration. This default configuration
53 * contains one {@link DataProvider data provider} for each component of the
54 * path-like list specified by the java property <code>orekit.data.path</code>.
55 * See the {@link #feed(String, DataLoader) feed} method documentation for further
56 * details. The default providers configuration is <em>not</em> set up if the list
57 * is not empty. If users want to have both the default providers and additional
58 * providers, they must call explicitly the {@link #addDefaultProviders()} method.
59 * </p>
60 *
61 * <p>
62 * The default configuration uses a predefined set of {@link DataFilter data filters}
63 * that already handled gzip-compressed files (recognized by the {@code .gz} suffix),
64 * Unix-compressed files (recognized by the {@code .Z} suffix) and Hatanaka compressed
65 * RINEX files. Users can access the {@link #getFiltersManager() filters manager} to
66 * set up custom filters for handling specific types of filters (decompression,
67 * 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 /** Manager for filters.
83 * @since 11.0
84 */
85 private final FiltersManager filtersManager;
86
87 /** Loaded data. */
88 private final Set<String> loaded;
89
90 /** Build an instance with default configuration. */
91 public DataProvidersManager() {
92 providers = new ArrayList<>();
93 filtersManager = new FiltersManager();
94 loaded = new LinkedHashSet<>();
95 resetFiltersToDefault();
96 }
97
98 /** Get the manager for filters.
99 * @return filters manager
100 * @since 11.0
101 */
102 public FiltersManager getFiltersManager() {
103 return filtersManager;
104 }
105
106 /** Reset all filters to default.
107 * <p>
108 * This method {@link FiltersManager#clearFilters() clears} the
109 * {@link #getFiltersManager() filter manager} and then
110 * {@link FiltersManager#addFilter(DataFilter) adds} back the
111 * default filters
112 * </p>
113 * @since 11.0
114 */
115 public void resetFiltersToDefault() {
116
117 // clear the existing filters
118 filtersManager.clearFilters();
119
120 // set up predefined filters
121 filtersManager.addFilter(new GzipFilter());
122 filtersManager.addFilter(new UnixCompressFilter());
123 filtersManager.addFilter(new HatanakaCompressFilter());
124
125 }
126
127 /** Add the default providers configuration.
128 * <p>
129 * The default configuration contains one {@link DataProvider data provider}
130 * for each component of the path-like list specified by the java property
131 * <code>orekit.data.path</code>.
132 * </p>
133 * <p>
134 * If the property is not set or is null, no data will be available to the library
135 * (for example no pole corrections will be applied and only predefined UTC steps
136 * will be taken into account). No errors will be triggered in this case.
137 * </p>
138 * <p>
139 * If the property is set, it must contains a list of existing directories or zip/jar
140 * archives. One {@link DirectoryCrawler} instance will be set up for each
141 * directory and one {@link ZipJarCrawler} instance (configured to look for the
142 * archive in the filesystem) will be set up for each zip/jar archive. The list
143 * elements in the java property are separated using the standard path separator for
144 * the operating system as returned by {@link System#getProperty(String)
145 * System.getProperty("path.separator")}. This standard path separator is ":" on
146 * Linux and Unix type systems and ";" on Windows types systems.
147 * </p>
148 */
149 public void addDefaultProviders() {
150
151 // get the path containing all components
152 final String path = System.getProperty(OREKIT_DATA_PATH);
153 if (path != null && !"".equals(path)) {
154
155 // extract the various components
156 for (final String name : path.split(System.getProperty("path.separator"))) {
157 if (!"".equals(name)) {
158
159 final File file = new File(name);
160
161 // check component
162 if (!file.exists()) {
163 if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
164 throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, name);
165 } else {
166 throw new OrekitException(OrekitMessages.DATA_ROOT_DIRECTORY_DOES_NOT_EXIST, name);
167 }
168 }
169
170 if (file.isDirectory()) {
171 addProvider(new DirectoryCrawler(file));
172 } else if (DataProvider.ZIP_ARCHIVE_PATTERN.matcher(name).matches()) {
173 addProvider(new ZipJarCrawler(file));
174 } else {
175 throw new OrekitException(OrekitMessages.NEITHER_DIRECTORY_NOR_ZIP_OR_JAR, name);
176 }
177
178 }
179 }
180 }
181
182 }
183
184 /** Add a data provider to the supported list.
185 * @param provider data provider to add
186 * @see #removeProvider(DataProvider)
187 * @see #clearProviders()
188 * @see #isSupported(DataProvider)
189 * @see #getProviders()
190 */
191 public void addProvider(final DataProvider provider) {
192 providers.add(provider);
193 }
194
195 /** Remove one provider.
196 * @param provider provider instance to remove
197 * @return instance removed (null if the provider was not already present)
198 * @see #addProvider(DataProvider)
199 * @see #clearProviders()
200 * @see #isSupported(DataProvider)
201 * @see #getProviders()
202 * @since 5.1
203 */
204 public DataProvider removeProvider(final DataProvider provider) {
205 for (final Iterator<DataProvider> iterator = providers.iterator(); iterator.hasNext();) {
206 final DataProvider current = iterator.next();
207 if (current == provider) {
208 iterator.remove();
209 return provider;
210 }
211 }
212 return null;
213 }
214
215 /** Remove all data providers.
216 * @see #addProvider(DataProvider)
217 * @see #removeProvider(DataProvider)
218 * @see #isSupported(DataProvider)
219 * @see #getProviders()
220 */
221 public void clearProviders() {
222 providers.clear();
223 }
224
225 /** Check if some provider is supported.
226 * @param provider provider to check
227 * @return true if the specified provider instance is already in the supported list
228 * @see #addProvider(DataProvider)
229 * @see #removeProvider(DataProvider)
230 * @see #clearProviders()
231 * @see #getProviders()
232 * @since 5.1
233 */
234 public boolean isSupported(final DataProvider provider) {
235 for (final DataProvider current : providers) {
236 if (current == provider) {
237 return true;
238 }
239 }
240 return false;
241 }
242
243 /** Get an unmodifiable view of the list of supported providers.
244 * @return unmodifiable view of the list of supported providers
245 * @see #addProvider(DataProvider)
246 * @see #removeProvider(DataProvider)
247 * @see #clearProviders()
248 * @see #isSupported(DataProvider)
249 */
250 public List<DataProvider> getProviders() {
251 return Collections.unmodifiableList(providers);
252 }
253
254 /** Get an unmodifiable view of the set of data file names that have been loaded.
255 * <p>
256 * The names returned are exactly the ones that were given to the {@link
257 * DataLoader#loadData(InputStream, String) DataLoader.loadData} method.
258 * </p>
259 * @return unmodifiable view of the set of data file names that have been loaded
260 * @see #feed(String, DataLoader)
261 * @see #clearLoadedDataNames()
262 */
263 public Set<String> getLoadedDataNames() {
264 return Collections.unmodifiableSet(loaded);
265 }
266
267 /** Clear the set of data file names that have been loaded.
268 * @see #getLoadedDataNames()
269 */
270 public void clearLoadedDataNames() {
271 loaded.clear();
272 }
273
274 /** Feed a data file loader by browsing all data providers.
275 * <p>
276 * If this method is called with an empty list of providers, a default
277 * providers configuration is set up. This default configuration contains
278 * only one {@link DataProvider data provider}: a {@link DirectoryCrawler}
279 * instance that loads data from files located somewhere in a directory hierarchy.
280 * This default provider is <em>not</em> added if the list is not empty. If users
281 * want to have both the default provider and other providers, they must add it
282 * explicitly.
283 * </p>
284 * <p>
285 * The providers are used in the order in which they were {@link #addProvider(DataProvider)
286 * added}. As soon as one provider is able to feed the data loader, the loop is
287 * stopped. If no provider is able to feed the data loader, then the last error
288 * triggered is thrown.
289 * </p>
290 * @param supportedNames regular expression for file names supported by the visitor
291 * @param loader data loader to use
292 * @return true if some data has been loaded
293 */
294 public boolean feed(final String supportedNames, final DataLoader loader) {
295
296 final Pattern supported = Pattern.compile(supportedNames);
297
298 // set up a default configuration if no providers have been set
299 if (providers.isEmpty()) {
300 addDefaultProviders();
301 }
302
303 // monitor the data that the loader will load
304 final DataLoader monitoredLoader = new MonitoringWrapper(loader);
305
306 // crawl the data collection
307 OrekitException delayedException = null;
308 for (final DataProvider provider : providers) {
309 try {
310
311 // try to feed the visitor using the current provider
312 if (provider.feed(supported, monitoredLoader, this)) {
313 return true;
314 }
315
316 } catch (OrekitException oe) {
317 // remember the last error encountered
318 delayedException = oe;
319 }
320 }
321
322 if (delayedException != null) {
323 throw delayedException;
324 }
325
326 return false;
327
328 }
329
330 /** Data loading monitoring wrapper class. */
331 private class MonitoringWrapper implements DataLoader {
332
333 /** Wrapped loader. */
334 private final DataLoader loader;
335
336 /** Simple constructor.
337 * @param loader loader to monitor
338 */
339 MonitoringWrapper(final DataLoader loader) {
340 this.loader = loader;
341 }
342
343 /** {@inheritDoc} */
344 public boolean stillAcceptsData() {
345 // delegate to monitored loader
346 return loader.stillAcceptsData();
347 }
348
349 /** {@inheritDoc} */
350 public void loadData(final InputStream input, final String name)
351 throws IOException, ParseException, OrekitException {
352
353 // delegate to monitored loader
354 loader.loadData(input, name);
355
356 // monitor the fact new data has been loaded
357 loaded.add(name);
358
359 }
360
361 }
362
363 }