1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.configuration;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.PrintStream;
24 import java.io.PrintWriter;
25 import java.io.StringWriter;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
28 import java.net.MalformedURLException;
29 import java.net.URL;
30 import java.net.URLDecoder;
31 import java.util.Iterator;
32
33 import org.apache.commons.configuration.event.ConfigurationErrorEvent;
34 import org.apache.commons.configuration.event.ConfigurationErrorListener;
35 import org.apache.commons.configuration.event.EventSource;
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39
40 /***
41 * Miscellaneous utility methods for configurations.
42 *
43 * @see ConfigurationConverter Utility methods to convert configurations.
44 *
45 * @author <a href="mailto:herve.quiroz@esil.univ-mrs.fr">Herve Quiroz</a>
46 * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
47 * @author Emmanuel Bourg
48 * @version $Revision: 567771 $, $Date: 2007-08-20 19:57:08 +0200 (Mo, 20 Aug 2007) $
49 */
50 public final class ConfigurationUtils
51 {
52 /*** Constant for the file URL protocol.*/
53 static final String PROTOCOL_FILE = "file";
54
55 /*** Constant for the resource path separator.*/
56 static final String RESOURCE_PATH_SEPARATOR = "/";
57
58 /*** Constant for the name of the clone() method.*/
59 private static final String METHOD_CLONE = "clone";
60
61 /*** The logger.*/
62 private static Log log = LogFactory.getLog(ConfigurationUtils.class);
63
64 /***
65 * Private constructor. Prevents instances from being created.
66 */
67 private ConfigurationUtils()
68 {
69
70 }
71
72 /***
73 * Dump the configuration key/value mappings to some ouput stream.
74 *
75 * @param configuration the configuration
76 * @param out the output stream to dump the configuration to
77 */
78 public static void dump(Configuration configuration, PrintStream out)
79 {
80 dump(configuration, new PrintWriter(out));
81 }
82
83 /***
84 * Dump the configuration key/value mappings to some writer.
85 *
86 * @param configuration the configuration
87 * @param out the writer to dump the configuration to
88 */
89 public static void dump(Configuration configuration, PrintWriter out)
90 {
91 Iterator keys = configuration.getKeys();
92 while (keys.hasNext())
93 {
94 String key = (String) keys.next();
95 Object value = configuration.getProperty(key);
96 out.print(key);
97 out.print("=");
98 out.print(value);
99
100 if (keys.hasNext())
101 {
102 out.println();
103 }
104 }
105
106 out.flush();
107 }
108
109 /***
110 * Get a string representation of the key/value mappings of a
111 * configuration.
112 *
113 * @param configuration the configuration
114 * @return a string representation of the configuration
115 */
116 public static String toString(Configuration configuration)
117 {
118 StringWriter writer = new StringWriter();
119 dump(configuration, new PrintWriter(writer));
120 return writer.toString();
121 }
122
123 /***
124 * <p>Copy all properties from the source configuration to the target
125 * configuration. Properties in the target configuration are replaced with
126 * the properties with the same key in the source configuration.</p>
127 * <p><em>Note:</em> This method is not able to handle some specifics of
128 * configurations derived from <code>AbstractConfiguration</code> (e.g.
129 * list delimiters). For a full support of all of these features the
130 * <code>copy()</code> method of <code>AbstractConfiguration</code> should
131 * be used. In a future release this method might become deprecated.</p>
132 *
133 * @param source the source configuration
134 * @param target the target configuration
135 * @since 1.1
136 */
137 public static void copy(Configuration source, Configuration target)
138 {
139 Iterator keys = source.getKeys();
140 while (keys.hasNext())
141 {
142 String key = (String) keys.next();
143 target.setProperty(key, source.getProperty(key));
144 }
145 }
146
147 /***
148 * <p>Append all properties from the source configuration to the target
149 * configuration. Properties in the source configuration are appended to
150 * the properties with the same key in the target configuration.</p>
151 * <p><em>Note:</em> This method is not able to handle some specifics of
152 * configurations derived from <code>AbstractConfiguration</code> (e.g.
153 * list delimiters). For a full support of all of these features the
154 * <code>copy()</code> method of <code>AbstractConfiguration</code> should
155 * be used. In a future release this method might become deprecated.</p>
156 *
157 * @param source the source configuration
158 * @param target the target configuration
159 * @since 1.1
160 */
161 public static void append(Configuration source, Configuration target)
162 {
163 Iterator keys = source.getKeys();
164 while (keys.hasNext())
165 {
166 String key = (String) keys.next();
167 target.addProperty(key, source.getProperty(key));
168 }
169 }
170
171 /***
172 * Converts the passed in configuration to a hierarchical one. If the
173 * configuration is already hierarchical, it is directly returned. Otherwise
174 * all properties are copied into a new hierarchical configuration.
175 *
176 * @param conf the configuration to convert
177 * @return the new hierarchical configuration (the result is <b>null</b> if
178 * and only if the passed in configuration is <b>null</b>)
179 * @since 1.3
180 */
181 public static HierarchicalConfiguration convertToHierarchical(
182 Configuration conf)
183 {
184 if (conf == null)
185 {
186 return null;
187 }
188
189 if (conf instanceof HierarchicalConfiguration)
190 {
191 return (HierarchicalConfiguration) conf;
192 }
193 else
194 {
195 HierarchicalConfiguration hc = new HierarchicalConfiguration();
196
197 boolean delimiterParsingStatus = hc.isDelimiterParsingDisabled();
198 hc.setDelimiterParsingDisabled(true);
199 ConfigurationUtils.copy(conf, hc);
200 hc.setDelimiterParsingDisabled(delimiterParsingStatus);
201 return hc;
202 }
203 }
204
205 /***
206 * Clones the given configuration object if this is possible. If the passed
207 * in configuration object implements the <code>Cloneable</code>
208 * interface, its <code>clone()</code> method will be invoked. Otherwise
209 * an exception will be thrown.
210 *
211 * @param config the configuration object to be cloned (can be <b>null</b>)
212 * @return the cloned configuration (<b>null</b> if the argument was
213 * <b>null</b>, too)
214 * @throws ConfigurationRuntimeException if cloning is not supported for
215 * this object
216 * @since 1.3
217 */
218 public static Configuration cloneConfiguration(Configuration config)
219 throws ConfigurationRuntimeException
220 {
221 if (config == null)
222 {
223 return null;
224 }
225 else
226 {
227 try
228 {
229 return (Configuration) clone(config);
230 }
231 catch (CloneNotSupportedException cnex)
232 {
233 throw new ConfigurationRuntimeException(cnex);
234 }
235 }
236 }
237
238 /***
239 * An internally used helper method for cloning objects. This implementation
240 * is not very sophisticated nor efficient. Maybe it can be replaced by an
241 * implementation from Commons Lang later. The method checks whether the
242 * passed in object implements the <code>Cloneable</code> interface. If
243 * this is the case, the <code>clone()</code> method is invoked by
244 * reflection. Errors that occur during the cloning process are re-thrown as
245 * runtime exceptions.
246 *
247 * @param obj the object to be cloned
248 * @return the cloned object
249 * @throws CloneNotSupportedException if the object cannot be cloned
250 */
251 static Object clone(Object obj) throws CloneNotSupportedException
252 {
253 if (obj instanceof Cloneable)
254 {
255 try
256 {
257 Method m = obj.getClass().getMethod(METHOD_CLONE, null);
258 return m.invoke(obj, null);
259 }
260 catch (NoSuchMethodException nmex)
261 {
262 throw new CloneNotSupportedException(
263 "No clone() method found for class"
264 + obj.getClass().getName());
265 }
266 catch (IllegalAccessException iaex)
267 {
268 throw new ConfigurationRuntimeException(iaex);
269 }
270 catch (InvocationTargetException itex)
271 {
272 throw new ConfigurationRuntimeException(itex);
273 }
274 }
275 else
276 {
277 throw new CloneNotSupportedException(obj.getClass().getName()
278 + " does not implement Cloneable");
279 }
280 }
281
282 /***
283 * Constructs a URL from a base path and a file name. The file name can
284 * be absolute, relative or a full URL. If necessary the base path URL is
285 * applied.
286 *
287 * @param basePath the base path URL (can be <b>null</b>)
288 * @param file the file name
289 * @return the resulting URL
290 * @throws MalformedURLException if URLs are invalid
291 */
292 public static URL getURL(String basePath, String file) throws MalformedURLException
293 {
294 File f = new File(file);
295 if (f.isAbsolute())
296 {
297 return f.toURL();
298 }
299
300 try
301 {
302 if (basePath == null)
303 {
304 return new URL(file);
305 }
306 else
307 {
308 URL base = new URL(basePath);
309 return new URL(base, file);
310 }
311 }
312 catch (MalformedURLException uex)
313 {
314 return constructFile(basePath, file).toURL();
315 }
316 }
317
318 /***
319 * Helper method for constructing a file object from a base path and a
320 * file name. This method is called if the base path passed to
321 * <code>getURL()</code> does not seem to be a valid URL.
322 *
323 * @param basePath the base path
324 * @param fileName the file name
325 * @return the resulting file
326 */
327 static File constructFile(String basePath, String fileName)
328 {
329 File file = null;
330
331 File absolute = null;
332 if (fileName != null)
333 {
334 absolute = new File(fileName);
335 }
336
337 if (StringUtils.isEmpty(basePath) || (absolute != null && absolute.isAbsolute()))
338 {
339 file = new File(fileName);
340 }
341 else
342 {
343 StringBuffer fName = new StringBuffer();
344 fName.append(basePath);
345
346
347 if (!basePath.endsWith(File.separator))
348 {
349 fName.append(File.separator);
350 }
351
352
353
354
355
356
357
358 if (fileName.startsWith("." + File.separator))
359 {
360 fName.append(fileName.substring(2));
361 }
362 else
363 {
364 fName.append(fileName);
365 }
366
367 file = new File(fName.toString());
368 }
369
370 return file;
371 }
372
373 /***
374 * Return the location of the specified resource by searching the user home
375 * directory, the current classpath and the system classpath.
376 *
377 * @param name the name of the resource
378 *
379 * @return the location of the resource
380 */
381 public static URL locate(String name)
382 {
383 return locate(null, name);
384 }
385
386 /***
387 * Return the location of the specified resource by searching the user home
388 * directory, the current classpath and the system classpath.
389 *
390 * @param base the base path of the resource
391 * @param name the name of the resource
392 *
393 * @return the location of the resource
394 */
395 public static URL locate(String base, String name)
396 {
397 if (log.isDebugEnabled())
398 {
399 StringBuffer buf = new StringBuffer();
400 buf.append("ConfigurationUtils.locate(): base is ").append(base);
401 buf.append(", name is ").append(name);
402 log.debug(buf.toString());
403 }
404
405 if (name == null)
406 {
407
408 return null;
409 }
410
411 URL url = null;
412
413
414 try
415 {
416 if (base == null)
417 {
418 url = new URL(name);
419 }
420 else
421 {
422 URL baseURL = new URL(base);
423 url = new URL(baseURL, name);
424
425
426 InputStream in = null;
427 try
428 {
429 in = url.openStream();
430 }
431 finally
432 {
433 if (in != null)
434 {
435 in.close();
436 }
437 }
438 }
439
440 log.debug("Loading configuration from the URL " + url);
441 }
442 catch (IOException e)
443 {
444 url = null;
445 }
446
447
448 if (url == null)
449 {
450 File file = new File(name);
451 if (file.isAbsolute() && file.exists())
452 {
453 try
454 {
455 url = file.toURL();
456 log.debug("Loading configuration from the absolute path " + name);
457 }
458 catch (MalformedURLException e)
459 {
460 log.warn("Could not obtain URL from file", e);
461 }
462 }
463 }
464
465
466 if (url == null)
467 {
468 try
469 {
470 File file = constructFile(base, name);
471 if (file != null && file.exists())
472 {
473 url = file.toURL();
474 }
475
476 if (url != null)
477 {
478 log.debug("Loading configuration from the path " + file);
479 }
480 }
481 catch (MalformedURLException e)
482 {
483 log.warn("Could not obtain URL from file", e);
484 }
485 }
486
487
488 if (url == null)
489 {
490 try
491 {
492 File file = constructFile(System.getProperty("user.home"), name);
493 if (file != null && file.exists())
494 {
495 url = file.toURL();
496 }
497
498 if (url != null)
499 {
500 log.debug("Loading configuration from the home path " + file);
501 }
502
503 }
504 catch (MalformedURLException e)
505 {
506 log.warn("Could not obtain URL from file", e);
507 }
508 }
509
510
511 if (url == null)
512 {
513 url = locateFromClasspath(name);
514 }
515 return url;
516 }
517
518 /***
519 * Tries to find a resource with the given name in the classpath.
520 * @param resourceName the name of the resource
521 * @return the URL to the found resource or <b>null</b> if the resource
522 * cannot be found
523 */
524 static URL locateFromClasspath(String resourceName)
525 {
526 URL url = null;
527
528 ClassLoader loader = Thread.currentThread().getContextClassLoader();
529 if (loader != null)
530 {
531 url = loader.getResource(resourceName);
532
533 if (url != null)
534 {
535 log.debug("Loading configuration from the context classpath (" + resourceName + ")");
536 }
537 }
538
539
540 if (url == null)
541 {
542 url = ClassLoader.getSystemResource(resourceName);
543
544 if (url != null)
545 {
546 log.debug("Loading configuration from the system classpath (" + resourceName + ")");
547 }
548 }
549 return url;
550 }
551
552 /***
553 * Return the path without the file name, for example http://xyz.net/foo/bar.xml
554 * results in http://xyz.net/foo/
555 *
556 * @param url the URL from which to extract the path
557 * @return the path component of the passed in URL
558 */
559 static String getBasePath(URL url)
560 {
561 if (url == null)
562 {
563 return null;
564 }
565
566 String s = url.toString();
567
568 if (s.endsWith("/") || StringUtils.isEmpty(url.getPath()))
569 {
570 return s;
571 }
572 else
573 {
574 return s.substring(0, s.lastIndexOf("/") + 1);
575 }
576 }
577
578 /***
579 * Extract the file name from the specified URL.
580 *
581 * @param url the URL from which to extract the file name
582 * @return the extracted file name
583 */
584 static String getFileName(URL url)
585 {
586 if (url == null)
587 {
588 return null;
589 }
590
591 String path = url.getPath();
592
593 if (path.endsWith("/") || StringUtils.isEmpty(path))
594 {
595 return null;
596 }
597 else
598 {
599 return path.substring(path.lastIndexOf("/") + 1);
600 }
601 }
602
603 /***
604 * Tries to convert the specified base path and file name into a file object.
605 * This method is called e.g. by the save() methods of file based
606 * configurations. The parameter strings can be relative files, absolute
607 * files and URLs as well. This implementation checks first whether the passed in
608 * file name is absolute. If this is the case, it is returned. Otherwise
609 * further checks are performed whether the base path and file name can be
610 * combined to a valid URL or a valid file name. <em>Note:</em> The test
611 * if the passed in file name is absolute is performed using
612 * <code>java.io.File.isAbsolute()</code>. If the file name starts with a
613 * slash, this method will return <b>true</b> on Unix, but <b>false</b> on
614 * Windows. So to ensure correct behavior for relative file names on all
615 * platforms you should never let relative paths start with a slash. E.g.
616 * in a configuration definition file do not use something like that:
617 * <pre>
618 * <properties fileName="/subdir/my.properties"/>
619 * </pre>
620 * Under Windows this path would be resolved relative to the configuration
621 * definition file. Under Unix this would be treated as an absolute path
622 * name.
623 *
624 * @param basePath the base path
625 * @param fileName the file name
626 * @return the file object (<b>null</b> if no file can be obtained)
627 */
628 public static File getFile(String basePath, String fileName)
629 {
630
631 File f = new File(fileName);
632 if (f.isAbsolute())
633 {
634 return f;
635 }
636
637
638 URL url;
639 try
640 {
641 url = new URL(new URL(basePath), fileName);
642 }
643 catch (MalformedURLException mex1)
644 {
645 try
646 {
647 url = new URL(fileName);
648 }
649 catch (MalformedURLException mex2)
650 {
651 url = null;
652 }
653 }
654
655 if (url != null)
656 {
657 return fileFromURL(url);
658 }
659
660 return constructFile(basePath, fileName);
661 }
662
663 /***
664 * Tries to convert the specified URL to a file object. If this fails,
665 * <b>null</b> is returned.
666 *
667 * @param url the URL
668 * @return the resulting file object
669 */
670 public static File fileFromURL(URL url)
671 {
672 if (PROTOCOL_FILE.equals(url.getProtocol()))
673 {
674 return new File(URLDecoder.decode(url.getPath()));
675 }
676 else
677 {
678 return null;
679 }
680 }
681
682 /***
683 * Enables runtime exceptions for the specified configuration object. This
684 * method can be used for configuration implementations that may face errors
685 * on normal property access, e.g. <code>DatabaseConfiguration</code> or
686 * <code>JNDIConfiguration</code>. Per default such errors are simply
687 * logged and then ignored. This implementation will register a special
688 * <code>{@link ConfigurationErrorListener}</code> that throws a runtime
689 * exception (namely a <code>ConfigurationRuntimeException</code>) on
690 * each received error event.
691 *
692 * @param src the configuration, for which runtime exceptions are to be
693 * enabled; this configuration must be derived from
694 * <code>{@link EventSource}</code>
695 */
696 public static void enableRuntimeExceptions(Configuration src)
697 {
698 if (!(src instanceof EventSource))
699 {
700 throw new IllegalArgumentException(
701 "Configuration must be derived from EventSource!");
702 }
703 ((EventSource) src).addErrorListener(new ConfigurationErrorListener()
704 {
705 public void configurationError(ConfigurationErrorEvent event)
706 {
707
708 throw new ConfigurationRuntimeException(event.getCause());
709 }
710 });
711 }
712 }