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: 503227 $, $Date: 2007-02-03 17:19:15 +0100 (Sa, 03 Feb 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 * 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.
127 * <em>Note:</em> This method won't work well on hierarchical configurations
128 * because it is not able to copy information about the properties'
129 * structure. So when dealing with hierarchical configuration objects their
130 * <code>{@link HierarchicalConfiguration#clone() clone()}</code> methods
131 * should be used.
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 * 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.
151 *
152 * @param source the source configuration
153 * @param target the target configuration
154 * @since 1.1
155 */
156 public static void append(Configuration source, Configuration target)
157 {
158 Iterator keys = source.getKeys();
159 while (keys.hasNext())
160 {
161 String key = (String) keys.next();
162 target.addProperty(key, source.getProperty(key));
163 }
164 }
165
166 /***
167 * Converts the passed in configuration to a hierarchical one. If the
168 * configuration is already hierarchical, it is directly returned. Otherwise
169 * all properties are copied into a new hierarchical configuration.
170 *
171 * @param conf the configuration to convert
172 * @return the new hierarchical configuration (the result is <b>null</b> if
173 * and only if the passed in configuration is <b>null</b>)
174 * @since 1.3
175 */
176 public static HierarchicalConfiguration convertToHierarchical(
177 Configuration conf)
178 {
179 if (conf == null)
180 {
181 return null;
182 }
183
184 if (conf instanceof HierarchicalConfiguration)
185 {
186 return (HierarchicalConfiguration) conf;
187 }
188 else
189 {
190 HierarchicalConfiguration hc = new HierarchicalConfiguration();
191 ConfigurationUtils.copy(conf, hc);
192 return hc;
193 }
194 }
195
196 /***
197 * Clones the given configuration object if this is possible. If the passed
198 * in configuration object implements the <code>Cloneable</code>
199 * interface, its <code>clone()</code> method will be invoked. Otherwise
200 * an exception will be thrown.
201 *
202 * @param config the configuration object to be cloned (can be <b>null</b>)
203 * @return the cloned configuration (<b>null</b> if the argument was
204 * <b>null</b>, too)
205 * @throws ConfigurationRuntimeException if cloning is not supported for
206 * this object
207 * @since 1.3
208 */
209 public static Configuration cloneConfiguration(Configuration config)
210 throws ConfigurationRuntimeException
211 {
212 if (config == null)
213 {
214 return null;
215 }
216 else
217 {
218 try
219 {
220 return (Configuration) clone(config);
221 }
222 catch (CloneNotSupportedException cnex)
223 {
224 throw new ConfigurationRuntimeException(cnex);
225 }
226 }
227 }
228
229 /***
230 * An internally used helper method for cloning objects. This implementation
231 * is not very sophisticated nor efficient. Maybe it can be replaced by an
232 * implementation from Commons Lang later. The method checks whether the
233 * passed in object implements the <code>Cloneable</code> interface. If
234 * this is the case, the <code>clone()</code> method is invoked by
235 * reflection. Errors that occur during the cloning process are re-thrown as
236 * runtime exceptions.
237 *
238 * @param obj the object to be cloned
239 * @return the cloned object
240 * @throws CloneNotSupportedException if the object cannot be cloned
241 */
242 static Object clone(Object obj) throws CloneNotSupportedException
243 {
244 if (obj instanceof Cloneable)
245 {
246 try
247 {
248 Method m = obj.getClass().getMethod(METHOD_CLONE, null);
249 return m.invoke(obj, null);
250 }
251 catch (NoSuchMethodException nmex)
252 {
253 throw new CloneNotSupportedException(
254 "No clone() method found for class"
255 + obj.getClass().getName());
256 }
257 catch (IllegalAccessException iaex)
258 {
259 throw new ConfigurationRuntimeException(iaex);
260 }
261 catch (InvocationTargetException itex)
262 {
263 throw new ConfigurationRuntimeException(itex);
264 }
265 }
266 else
267 {
268 throw new CloneNotSupportedException(obj.getClass().getName()
269 + " does not implement Cloneable");
270 }
271 }
272
273 /***
274 * Constructs a URL from a base path and a file name. The file name can
275 * be absolute, relative or a full URL. If necessary the base path URL is
276 * applied.
277 *
278 * @param basePath the base path URL (can be <b>null</b>)
279 * @param file the file name
280 * @return the resulting URL
281 * @throws MalformedURLException if URLs are invalid
282 */
283 public static URL getURL(String basePath, String file) throws MalformedURLException
284 {
285 File f = new File(file);
286 if (f.isAbsolute())
287 {
288 return f.toURL();
289 }
290
291 try
292 {
293 if (basePath == null)
294 {
295 return new URL(file);
296 }
297 else
298 {
299 URL base = new URL(basePath);
300 return new URL(base, file);
301 }
302 }
303 catch (MalformedURLException uex)
304 {
305 return constructFile(basePath, file).toURL();
306 }
307 }
308
309 /***
310 * Helper method for constructing a file object from a base path and a
311 * file name. This method is called if the base path passed to
312 * <code>getURL()</code> does not seem to be a valid URL.
313 *
314 * @param basePath the base path
315 * @param fileName the file name
316 * @return the resulting file
317 */
318 static File constructFile(String basePath, String fileName)
319 {
320 File file = null;
321
322 File absolute = null;
323 if (fileName != null)
324 {
325 absolute = new File(fileName);
326 }
327
328 if (StringUtils.isEmpty(basePath) || (absolute != null && absolute.isAbsolute()))
329 {
330 file = new File(fileName);
331 }
332 else
333 {
334 StringBuffer fName = new StringBuffer();
335 fName.append(basePath);
336
337
338 if (!basePath.endsWith(File.separator))
339 {
340 fName.append(File.separator);
341 }
342
343
344
345
346
347
348
349 if (fileName.startsWith("." + File.separator))
350 {
351 fName.append(fileName.substring(2));
352 }
353 else
354 {
355 fName.append(fileName);
356 }
357
358 file = new File(fName.toString());
359 }
360
361 return file;
362 }
363
364 /***
365 * Return the location of the specified resource by searching the user home
366 * directory, the current classpath and the system classpath.
367 *
368 * @param name the name of the resource
369 *
370 * @return the location of the resource
371 */
372 public static URL locate(String name)
373 {
374 return locate(null, name);
375 }
376
377 /***
378 * Return the location of the specified resource by searching the user home
379 * directory, the current classpath and the system classpath.
380 *
381 * @param base the base path of the resource
382 * @param name the name of the resource
383 *
384 * @return the location of the resource
385 */
386 public static URL locate(String base, String name)
387 {
388 if (log.isDebugEnabled())
389 {
390 StringBuffer buf = new StringBuffer();
391 buf.append("ConfigurationUtils.locate(): base is ").append(base);
392 buf.append(", name is ").append(name);
393 log.debug(buf.toString());
394 }
395
396 if (name == null)
397 {
398
399 return null;
400 }
401
402 URL url = null;
403
404
405 try
406 {
407 if (base == null)
408 {
409 url = new URL(name);
410 }
411 else
412 {
413 URL baseURL = new URL(base);
414 url = new URL(baseURL, name);
415
416
417 InputStream in = null;
418 try
419 {
420 in = url.openStream();
421 }
422 finally
423 {
424 if (in != null)
425 {
426 in.close();
427 }
428 }
429 }
430
431 log.debug("Loading configuration from the URL " + url);
432 }
433 catch (IOException e)
434 {
435 url = null;
436 }
437
438
439 if (url == null)
440 {
441 File file = new File(name);
442 if (file.isAbsolute() && file.exists())
443 {
444 try
445 {
446 url = file.toURL();
447 log.debug("Loading configuration from the absolute path " + name);
448 }
449 catch (MalformedURLException e)
450 {
451 log.warn("Could not obtain URL from file", e);
452 }
453 }
454 }
455
456
457 if (url == null)
458 {
459 try
460 {
461 File file = constructFile(base, name);
462 if (file != null && file.exists())
463 {
464 url = file.toURL();
465 }
466
467 if (url != null)
468 {
469 log.debug("Loading configuration from the path " + file);
470 }
471 }
472 catch (MalformedURLException e)
473 {
474 log.warn("Could not obtain URL from file", e);
475 }
476 }
477
478
479 if (url == null)
480 {
481 try
482 {
483 File file = constructFile(System.getProperty("user.home"), name);
484 if (file != null && file.exists())
485 {
486 url = file.toURL();
487 }
488
489 if (url != null)
490 {
491 log.debug("Loading configuration from the home path " + file);
492 }
493
494 }
495 catch (MalformedURLException e)
496 {
497 log.warn("Could not obtain URL from file", e);
498 }
499 }
500
501
502 if (url == null)
503 {
504 url = locateFromClasspath(name);
505 }
506 return url;
507 }
508
509 /***
510 * Tries to find a resource with the given name in the classpath.
511 * @param resourceName the name of the resource
512 * @return the URL to the found resource or <b>null</b> if the resource
513 * cannot be found
514 */
515 static URL locateFromClasspath(String resourceName)
516 {
517 URL url = null;
518
519 ClassLoader loader = Thread.currentThread().getContextClassLoader();
520 if (loader != null)
521 {
522 url = loader.getResource(resourceName);
523
524 if (url != null)
525 {
526 log.debug("Loading configuration from the context classpath (" + resourceName + ")");
527 }
528 }
529
530
531 if (url == null)
532 {
533 url = ClassLoader.getSystemResource(resourceName);
534
535 if (url != null)
536 {
537 log.debug("Loading configuration from the system classpath (" + resourceName + ")");
538 }
539 }
540 return url;
541 }
542
543 /***
544 * Return the path without the file name, for example http://xyz.net/foo/bar.xml
545 * results in http://xyz.net/foo/
546 *
547 * @param url the URL from which to extract the path
548 * @return the path component of the passed in URL
549 */
550 static String getBasePath(URL url)
551 {
552 if (url == null)
553 {
554 return null;
555 }
556
557 String s = url.toString();
558
559 if (s.endsWith("/") || StringUtils.isEmpty(url.getPath()))
560 {
561 return s;
562 }
563 else
564 {
565 return s.substring(0, s.lastIndexOf("/") + 1);
566 }
567 }
568
569 /***
570 * Extract the file name from the specified URL.
571 *
572 * @param url the URL from which to extract the file name
573 * @return the extracted file name
574 */
575 static String getFileName(URL url)
576 {
577 if (url == null)
578 {
579 return null;
580 }
581
582 String path = url.getPath();
583
584 if (path.endsWith("/") || StringUtils.isEmpty(path))
585 {
586 return null;
587 }
588 else
589 {
590 return path.substring(path.lastIndexOf("/") + 1);
591 }
592 }
593
594 /***
595 * Tries to convert the specified base path and file name into a file object.
596 * This method is called e.g. by the save() methods of file based
597 * configurations. The parameter strings can be relative files, absolute
598 * files and URLs as well. This implementation checks first whether the passed in
599 * file name is absolute. If this is the case, it is returned. Otherwise
600 * further checks are performed whether the base path and file name can be
601 * combined to a valid URL or a valid file name. <em>Note:</em> The test
602 * if the passed in file name is absolute is performed using
603 * <code>java.io.File.isAbsolute()</code>. If the file name starts with a
604 * slash, this method will return <b>true</b> on Unix, but <b>false</b> on
605 * Windows. So to ensure correct behavior for relative file names on all
606 * platforms you should never let relative paths start with a slash. E.g.
607 * in a configuration definition file do not use something like that:
608 * <pre>
609 * <properties fileName="/subdir/my.properties"/>
610 * </pre>
611 * Under Windows this path would be resolved relative to the configuration
612 * definition file. Under Unix this would be treated as an absolute path
613 * name.
614 *
615 * @param basePath the base path
616 * @param fileName the file name
617 * @return the file object (<b>null</b> if no file can be obtained)
618 */
619 public static File getFile(String basePath, String fileName)
620 {
621
622 File f = new File(fileName);
623 if (f.isAbsolute())
624 {
625 return f;
626 }
627
628
629 URL url;
630 try
631 {
632 url = new URL(new URL(basePath), fileName);
633 }
634 catch (MalformedURLException mex1)
635 {
636 try
637 {
638 url = new URL(fileName);
639 }
640 catch (MalformedURLException mex2)
641 {
642 url = null;
643 }
644 }
645
646 if (url != null)
647 {
648 return fileFromURL(url);
649 }
650
651 return constructFile(basePath, fileName);
652 }
653
654 /***
655 * Tries to convert the specified URL to a file object. If this fails,
656 * <b>null</b> is returned.
657 *
658 * @param url the URL
659 * @return the resulting file object
660 */
661 public static File fileFromURL(URL url)
662 {
663 if (PROTOCOL_FILE.equals(url.getProtocol()))
664 {
665 return new File(URLDecoder.decode(url.getPath()));
666 }
667 else
668 {
669 return null;
670 }
671 }
672
673 /***
674 * Enables runtime exceptions for the specified configuration object. This
675 * method can be used for configuration implementations that may face errors
676 * on normal property access, e.g. <code>DatabaseConfiguration</code> or
677 * <code>JNDIConfiguration</code>. Per default such errors are simply
678 * logged and then ignored. This implementation will register a special
679 * <code>{@link ConfigurationErrorListener}</code> that throws a runtime
680 * exception (namely a <code>ConfigurationRuntimeException</code>) on
681 * each received error event.
682 *
683 * @param src the configuration, for which runtime exceptions are to be
684 * enabled; this configuration must be derived from
685 * <code>{@link EventSource}</code>
686 */
687 public static void enableRuntimeExceptions(Configuration src)
688 {
689 if (!(src instanceof EventSource))
690 {
691 throw new IllegalArgumentException(
692 "Configuration must be derived from EventSource!");
693 }
694 ((EventSource) src).addErrorListener(new ConfigurationErrorListener()
695 {
696 public void configurationError(ConfigurationErrorEvent event)
697 {
698
699 throw new ConfigurationRuntimeException(event.getCause());
700 }
701 });
702 }
703 }