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.net.URL;
24 import java.util.Collection;
25 import java.util.Iterator;
26 import java.util.LinkedList;
27 import java.util.Map;
28
29 import org.apache.commons.configuration.plist.PropertyListConfiguration;
30 import org.apache.commons.configuration.plist.XMLPropertyListConfiguration;
31 import org.apache.commons.digester.AbstractObjectCreationFactory;
32 import org.apache.commons.digester.CallMethodRule;
33 import org.apache.commons.digester.Digester;
34 import org.apache.commons.digester.ObjectCreationFactory;
35 import org.apache.commons.digester.Substitutor;
36 import org.apache.commons.digester.substitution.MultiVariableExpander;
37 import org.apache.commons.digester.substitution.VariableSubstitutor;
38 import org.apache.commons.digester.xmlrules.DigesterLoader;
39 import org.apache.commons.lang.StringUtils;
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42 import org.xml.sax.Attributes;
43 import org.xml.sax.SAXException;
44
45 /***
46 * <p>
47 * Factory class to create a CompositeConfiguration from a .xml file using
48 * Digester. By default it can handle the Configurations from commons-
49 * configuration. If you need to add your own, then you can pass in your own
50 * digester rules to use. It is also namespace aware, by providing a
51 * digesterRuleNamespaceURI.
52 * </p>
53 * <p>
54 * <em>Note:</em> Almost all of the features provided by this class and many
55 * more are also available for the <code>{@link DefaultConfigurationBuilder}</code>
56 * class. <code>DefaultConfigurationBuilder</code> also has a more robust
57 * merge algorithm for constructing combined configurations. So it is
58 * recommended to use this class instead of <code>ConfigurationFactory</code>.
59 * </p>
60 *
61 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
62 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
63 * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
64 * @version $Id: ConfigurationFactory.java 524006 2007-03-30 09:33:17Z oheger $
65 */
66 public class ConfigurationFactory
67 {
68 /*** Constant for the root element in the info file.*/
69 private static final String SEC_ROOT = "configuration/";
70
71 /*** Constant for the override section.*/
72 private static final String SEC_OVERRIDE = SEC_ROOT + "override/";
73
74 /*** Constant for the additional section.*/
75 private static final String SEC_ADDITIONAL = SEC_ROOT + "additional/";
76
77 /*** Constant for the optional attribute.*/
78 private static final String ATTR_OPTIONAL = "optional";
79
80 /*** Constant for the fileName attribute.*/
81 private static final String ATTR_FILENAME = "fileName";
82
83 /*** Constant for the load method.*/
84 private static final String METH_LOAD = "load";
85
86 /*** Constant for the default base path (points to actual directory).*/
87 private static final String DEF_BASE_PATH = ".";
88
89 /*** static logger */
90 private static Log log = LogFactory.getLog(ConfigurationFactory.class);
91
92 /*** The XML file with the details about the configuration to load */
93 private String configurationFileName;
94
95 /*** The URL to the XML file with the details about the configuration to load. */
96 private URL configurationURL;
97
98 /***
99 * The implicit base path for included files. This path is determined by
100 * the configuration to load and used unless no other base path was
101 * explicitely specified.
102 */
103 private String implicitBasePath;
104
105 /*** The basePath to prefix file paths for file based property files. */
106 private String basePath;
107
108 /*** URL for xml digester rules file */
109 private URL digesterRules;
110
111 /*** The digester namespace to parse */
112 private String digesterRuleNamespaceURI;
113
114 /***
115 * Constructor
116 */
117 public ConfigurationFactory()
118 {
119 setBasePath(DEF_BASE_PATH);
120 }
121 /***
122 * Constructor with ConfigurationFile Name passed
123 *
124 * @param configurationFileName The path to the configuration file
125 */
126 public ConfigurationFactory(String configurationFileName)
127 {
128 setConfigurationFileName(configurationFileName);
129 }
130
131 /***
132 * Return the configuration provided by this factory. It loads the
133 * configuration file which is a XML description of the actual
134 * configurations to load. It can contain various different types of
135 * configuration, e.g. Properties, XML and JNDI.
136 *
137 * @return A Configuration object
138 * @throws ConfigurationException A generic exception that we had trouble during the
139 * loading of the configuration data.
140 */
141 public Configuration getConfiguration() throws ConfigurationException
142 {
143 Digester digester;
144 InputStream input = null;
145 ConfigurationBuilder builder = new ConfigurationBuilder();
146 URL url = getConfigurationURL();
147 try
148 {
149 if (url == null)
150 {
151 url = ConfigurationUtils.locate(implicitBasePath, getConfigurationFileName());
152 }
153 input = url.openStream();
154 }
155 catch (Exception e)
156 {
157 log.error("Exception caught opening stream to URL", e);
158 throw new ConfigurationException("Exception caught opening stream to URL", e);
159 }
160
161 if (getDigesterRules() == null)
162 {
163 digester = new Digester();
164 configureNamespace(digester);
165 initDefaultDigesterRules(digester);
166 }
167 else
168 {
169 digester = DigesterLoader.createDigester(getDigesterRules());
170
171
172 configureNamespace(digester);
173 }
174
175
176 digester.setUseContextClassLoader(true);
177
178 enableDigesterSubstitutor(digester);
179
180 digester.push(builder);
181
182 try
183 {
184 digester.parse(input);
185 input.close();
186 }
187 catch (SAXException saxe)
188 {
189 log.error("SAX Exception caught", saxe);
190 throw new ConfigurationException("SAX Exception caught", saxe);
191 }
192 catch (IOException ioe)
193 {
194 log.error("IO Exception caught", ioe);
195 throw new ConfigurationException("IO Exception caught", ioe);
196 }
197 return builder.getConfiguration();
198 }
199
200 /***
201 * Returns the configurationFile.
202 *
203 * @return The name of the configuration file. Can be null.
204 */
205 public String getConfigurationFileName()
206 {
207 return configurationFileName;
208 }
209
210 /***
211 * Sets the configurationFile.
212 *
213 * @param configurationFileName The name of the configurationFile to use.
214 */
215 public void setConfigurationFileName(String configurationFileName)
216 {
217 File file = new File(configurationFileName).getAbsoluteFile();
218 this.configurationFileName = file.getName();
219 implicitBasePath = file.getParent();
220 }
221
222 /***
223 * Returns the URL of the configuration file to be loaded.
224 *
225 * @return the URL of the configuration to load
226 */
227 public URL getConfigurationURL()
228 {
229 return configurationURL;
230 }
231
232 /***
233 * Sets the URL of the configuration to load. This configuration can be
234 * either specified by a file name or by a URL.
235 *
236 * @param url the URL of the configuration to load
237 */
238 public void setConfigurationURL(URL url)
239 {
240 configurationURL = url;
241 implicitBasePath = url.toString();
242 }
243
244 /***
245 * Returns the digesterRules.
246 *
247 * @return URL
248 */
249 public URL getDigesterRules()
250 {
251 return digesterRules;
252 }
253
254 /***
255 * Sets the digesterRules.
256 *
257 * @param digesterRules The digesterRules to set
258 */
259 public void setDigesterRules(URL digesterRules)
260 {
261 this.digesterRules = digesterRules;
262 }
263
264 /***
265 * Adds a substitutor to interpolate system properties
266 *
267 * @param digester The digester to which we add the substitutor
268 */
269 protected void enableDigesterSubstitutor(Digester digester)
270 {
271 Map systemProperties = System.getProperties();
272 MultiVariableExpander expander = new MultiVariableExpander();
273 expander.addSource("$", systemProperties);
274
275
276 Substitutor substitutor = new VariableSubstitutor(expander);
277 digester.setSubstitutor(substitutor);
278 }
279
280 /***
281 * Initializes the parsing rules for the default digester
282 *
283 * This allows the Configuration Factory to understand the default types:
284 * Properties, XML and JNDI. Two special sections are introduced:
285 * <code><override></code> and <code><additional></code>.
286 *
287 * @param digester The digester to configure
288 */
289 protected void initDefaultDigesterRules(Digester digester)
290 {
291 initDigesterSectionRules(digester, SEC_ROOT, false);
292 initDigesterSectionRules(digester, SEC_OVERRIDE, false);
293 initDigesterSectionRules(digester, SEC_ADDITIONAL, true);
294 }
295
296 /***
297 * Sets up digester rules for a specified section of the configuration
298 * info file.
299 *
300 * @param digester the current digester instance
301 * @param matchString specifies the section
302 * @param additional a flag if rules for the additional section are to be
303 * added
304 */
305 protected void initDigesterSectionRules(Digester digester, String matchString, boolean additional)
306 {
307 setupDigesterInstance(
308 digester,
309 matchString + "properties",
310 new PropertiesConfigurationFactory(),
311 METH_LOAD,
312 additional);
313
314 setupDigesterInstance(
315 digester,
316 matchString + "plist",
317 new PropertyListConfigurationFactory(),
318 METH_LOAD,
319 additional);
320
321 setupDigesterInstance(
322 digester,
323 matchString + "xml",
324 new FileConfigurationFactory(XMLConfiguration.class),
325 METH_LOAD,
326 additional);
327
328 setupDigesterInstance(
329 digester,
330 matchString + "hierarchicalXml",
331 new FileConfigurationFactory(XMLConfiguration.class),
332 METH_LOAD,
333 additional);
334
335 setupDigesterInstance(
336 digester,
337 matchString + "jndi",
338 new JNDIConfigurationFactory(),
339 null,
340 additional);
341
342 setupDigesterInstance(
343 digester,
344 matchString + "system",
345 new SystemConfigurationFactory(),
346 null,
347 additional);
348 }
349
350 /***
351 * Sets up digester rules for a configuration to be loaded.
352 *
353 * @param digester the current digester
354 * @param matchString the pattern to match with this rule
355 * @param factory an ObjectCreationFactory instance to use for creating new
356 * objects
357 * @param method the name of a method to be called or <b>null</b> for none
358 * @param additional a flag if rules for the additional section are to be
359 * added
360 */
361 protected void setupDigesterInstance(
362 Digester digester,
363 String matchString,
364 ObjectCreationFactory factory,
365 String method,
366 boolean additional)
367 {
368 if (additional)
369 {
370 setupUnionRules(digester, matchString);
371 }
372
373 digester.addFactoryCreate(matchString, factory);
374 digester.addSetProperties(matchString);
375
376 if (method != null)
377 {
378 digester.addRule(matchString, new CallOptionalMethodRule(method));
379 }
380
381 digester.addSetNext(matchString, "addConfiguration", Configuration.class.getName());
382 }
383
384 /***
385 * Sets up rules for configurations in the additional section.
386 *
387 * @param digester the current digester
388 * @param matchString the pattern to match with this rule
389 */
390 protected void setupUnionRules(Digester digester, String matchString)
391 {
392 digester.addObjectCreate(matchString,
393 AdditionalConfigurationData.class);
394 digester.addSetProperties(matchString);
395 digester.addSetNext(matchString, "addAdditionalConfig",
396 AdditionalConfigurationData.class.getName());
397 }
398
399 /***
400 * Returns the digesterRuleNamespaceURI.
401 *
402 * @return A String with the digesterRuleNamespaceURI.
403 */
404 public String getDigesterRuleNamespaceURI()
405 {
406 return digesterRuleNamespaceURI;
407 }
408
409 /***
410 * Sets the digesterRuleNamespaceURI.
411 *
412 * @param digesterRuleNamespaceURI The new digesterRuleNamespaceURI to use
413 */
414 public void setDigesterRuleNamespaceURI(String digesterRuleNamespaceURI)
415 {
416 this.digesterRuleNamespaceURI = digesterRuleNamespaceURI;
417 }
418
419 /***
420 * Configure the current digester to be namespace aware and to have
421 * a Configuration object to which all of the other configurations
422 * should be added
423 *
424 * @param digester The Digester to configure
425 */
426 private void configureNamespace(Digester digester)
427 {
428 if (getDigesterRuleNamespaceURI() != null)
429 {
430 digester.setNamespaceAware(true);
431 digester.setRuleNamespaceURI(getDigesterRuleNamespaceURI());
432 }
433 else
434 {
435 digester.setNamespaceAware(false);
436 }
437 digester.setValidating(false);
438 }
439
440 /***
441 * Returns the Base path from which this Configuration Factory operates.
442 * This is never null. If you set the BasePath to null, then a base path
443 * according to the configuration to load is returned.
444 *
445 * @return The base Path of this configuration factory.
446 */
447 public String getBasePath()
448 {
449 String path = StringUtils.isEmpty(basePath)
450 || DEF_BASE_PATH.equals(basePath) ? implicitBasePath : basePath;
451 return StringUtils.isEmpty(path) ? DEF_BASE_PATH : path;
452 }
453
454 /***
455 * Sets the basePath for all file references from this Configuration Factory.
456 * Normally a base path need not to be set because it is determined by
457 * the location of the configuration file to load. All relative pathes in
458 * this file are resolved relative to this file. Setting a base path makes
459 * sense if such relative pathes should be otherwise resolved, e.g. if
460 * the configuration file is loaded from the class path and all sub
461 * configurations it refers to are stored in a special config directory.
462 *
463 * @param basePath The new basePath to set.
464 */
465 public void setBasePath(String basePath)
466 {
467 this.basePath = basePath;
468 }
469
470 /***
471 * A base class for digester factory classes. This base class maintains
472 * a default class for the objects to be created.
473 * There will be sub classes for specific configuration implementations.
474 */
475 public class DigesterConfigurationFactory extends AbstractObjectCreationFactory
476 {
477 /*** Actual class to use. */
478 private Class clazz;
479
480 /***
481 * Creates a new instance of <code>DigesterConfigurationFactory</code>.
482 *
483 * @param clazz the class which we should instantiate
484 */
485 public DigesterConfigurationFactory(Class clazz)
486 {
487 this.clazz = clazz;
488 }
489
490 /***
491 * Creates an instance of the specified class.
492 *
493 * @param attribs the attributes (ignored)
494 * @return the new object
495 * @throws Exception if object creation fails
496 */
497 public Object createObject(Attributes attribs) throws Exception
498 {
499 return clazz.newInstance();
500 }
501 }
502
503 /***
504 * A tiny inner class that allows the Configuration Factory to
505 * let the digester construct FileConfiguration objects
506 * that already have the correct base Path set.
507 *
508 */
509 public class FileConfigurationFactory extends DigesterConfigurationFactory
510 {
511 /***
512 * C'tor
513 *
514 * @param clazz The class which we should instantiate.
515 */
516 public FileConfigurationFactory(Class clazz)
517 {
518 super(clazz);
519 }
520
521 /***
522 * Gets called by the digester.
523 *
524 * @param attributes the actual attributes
525 * @return the new object
526 * @throws Exception Couldn't instantiate the requested object.
527 */
528 public Object createObject(Attributes attributes) throws Exception
529 {
530 FileConfiguration conf = createConfiguration(attributes);
531 conf.setBasePath(getBasePath());
532 return conf;
533 }
534
535 /***
536 * Creates the object, a <code>FileConfiguration</code>.
537 *
538 * @param attributes the actual attributes
539 * @return the file configuration
540 * @throws Exception if the object could not be created
541 */
542 protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
543 {
544 return (FileConfiguration) super.createObject(attributes);
545 }
546 }
547
548 /***
549 * A factory that returns an XMLPropertiesConfiguration for .xml files
550 * and a PropertiesConfiguration for the others.
551 *
552 * @since 1.2
553 */
554 public class PropertiesConfigurationFactory extends FileConfigurationFactory
555 {
556 /***
557 * Creates a new instance of <code>PropertiesConfigurationFactory</code>.
558 */
559 public PropertiesConfigurationFactory()
560 {
561 super(null);
562 }
563
564 /***
565 * Creates the new configuration object. Based on the file name
566 * provided in the attributes either a <code>PropertiesConfiguration</code>
567 * or a <code>XMLPropertiesConfiguration</code> object will be
568 * returned.
569 *
570 * @param attributes the attributes
571 * @return the new configuration object
572 * @throws Exception if an error occurs
573 */
574 protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
575 {
576 String filename = attributes.getValue(ATTR_FILENAME);
577
578 if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
579 {
580 return new XMLPropertiesConfiguration();
581 }
582 else
583 {
584 return new PropertiesConfiguration();
585 }
586 }
587 }
588
589 /***
590 * A factory that returns an XMLPropertyListConfiguration for .xml files
591 * and a PropertyListConfiguration for the others.
592 *
593 * @since 1.2
594 */
595 public class PropertyListConfigurationFactory extends FileConfigurationFactory
596 {
597 /***
598 * Creates a new instance of <code>PropertyListConfigurationFactory</code>.
599 */
600 public PropertyListConfigurationFactory()
601 {
602 super(null);
603 }
604
605 /***
606 * Creates the new configuration object. Based on the file name
607 * provided in the attributes either a <code>XMLPropertyListConfiguration</code>
608 * or a <code>PropertyListConfiguration</code> object will be
609 * returned.
610 *
611 * @param attributes the attributes
612 * @return the new configuration object
613 * @throws Exception if an error occurs
614 */
615 protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
616 {
617 String filename = attributes.getValue(ATTR_FILENAME);
618
619 if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
620 {
621 return new XMLPropertyListConfiguration();
622 }
623 else
624 {
625 return new PropertyListConfiguration();
626 }
627 }
628 }
629
630 /***
631 * A tiny inner class that allows the Configuration Factory to
632 * let the digester construct JNDIConfiguration objects.
633 */
634 private class JNDIConfigurationFactory extends DigesterConfigurationFactory
635 {
636 /***
637 * Creates a new instance of <code>JNDIConfigurationFactory</code>.
638 */
639 public JNDIConfigurationFactory()
640 {
641 super(JNDIConfiguration.class);
642 }
643 }
644
645 /***
646 * A tiny inner class that allows the Configuration Factory to
647 * let the digester construct SystemConfiguration objects.
648 */
649 private class SystemConfigurationFactory extends DigesterConfigurationFactory
650 {
651 /***
652 * Creates a new instance of <code>SystemConfigurationFactory</code>.
653 */
654 public SystemConfigurationFactory()
655 {
656 super(SystemConfiguration.class);
657 }
658 }
659
660 /***
661 * A simple data class that holds all information about a configuration
662 * from the <code><additional></code> section.
663 */
664 public static class AdditionalConfigurationData
665 {
666 /*** Stores the configuration object.*/
667 private Configuration configuration;
668
669 /*** Stores the location of this configuration in the global tree.*/
670 private String at;
671
672 /***
673 * Returns the value of the <code>at</code> attribute.
674 *
675 * @return the at attribute
676 */
677 public String getAt()
678 {
679 return at;
680 }
681
682 /***
683 * Sets the value of the <code>at</code> attribute.
684 *
685 * @param string the attribute value
686 */
687 public void setAt(String string)
688 {
689 at = string;
690 }
691
692 /***
693 * Returns the configuration object.
694 *
695 * @return the configuration
696 */
697 public Configuration getConfiguration()
698 {
699 return configuration;
700 }
701
702 /***
703 * Sets the configuration object. Note: Normally this method should be
704 * named <code>setConfiguration()</code>, but the name
705 * <code>addConfiguration()</code> is required by some of the digester
706 * rules.
707 *
708 * @param config the configuration to set
709 */
710 public void addConfiguration(Configuration config)
711 {
712 configuration = config;
713 }
714 }
715
716 /***
717 * An internally used helper class for constructing the composite
718 * configuration object.
719 */
720 public static class ConfigurationBuilder
721 {
722 /*** Stores the composite configuration.*/
723 private CompositeConfiguration config;
724
725 /*** Stores a collection with the configs from the additional section.*/
726 private Collection additionalConfigs;
727
728 /***
729 * Creates a new instance of <code>ConfigurationBuilder</code>.
730 */
731 public ConfigurationBuilder()
732 {
733 config = new CompositeConfiguration();
734 additionalConfigs = new LinkedList();
735 }
736
737 /***
738 * Adds a new configuration to this object. This method is called by
739 * Digester.
740 *
741 * @param conf the configuration to be added
742 */
743 public void addConfiguration(Configuration conf)
744 {
745 config.addConfiguration(conf);
746 }
747
748 /***
749 * Adds information about an additional configuration. This method is
750 * called by Digester.
751 *
752 * @param data the data about the additional configuration
753 */
754 public void addAdditionalConfig(AdditionalConfigurationData data)
755 {
756 additionalConfigs.add(data);
757 }
758
759 /***
760 * Returns the final composite configuration.
761 *
762 * @return the final configuration object
763 */
764 public CompositeConfiguration getConfiguration()
765 {
766 if (!additionalConfigs.isEmpty())
767 {
768 Configuration unionConfig = createAdditionalConfiguration(additionalConfigs);
769 if (unionConfig != null)
770 {
771 addConfiguration(unionConfig);
772 }
773 additionalConfigs.clear();
774 }
775
776 return config;
777 }
778
779 /***
780 * Creates a configuration object with the union of all properties
781 * defined in the <code><additional></code> section. This
782 * implementation returns a <code>HierarchicalConfiguration</code>
783 * object.
784 *
785 * @param configs a collection with
786 * <code>AdditionalConfigurationData</code> objects
787 * @return the union configuration (can be <b>null</b>)
788 */
789 protected Configuration createAdditionalConfiguration(Collection configs)
790 {
791 HierarchicalConfiguration result = new HierarchicalConfiguration();
792
793 for (Iterator it = configs.iterator(); it.hasNext();)
794 {
795 AdditionalConfigurationData cdata =
796 (AdditionalConfigurationData) it.next();
797 result.addNodes(cdata.getAt(),
798 createRootNode(cdata).getChildren());
799 }
800
801 return result.isEmpty() ? null : result;
802 }
803
804 /***
805 * Creates a configuration root node for the specified configuration.
806 *
807 * @param cdata the configuration data object
808 * @return a root node for this configuration
809 */
810 private HierarchicalConfiguration.Node createRootNode(AdditionalConfigurationData cdata)
811 {
812 if (cdata.getConfiguration() instanceof HierarchicalConfiguration)
813 {
814
815 return ((HierarchicalConfiguration) cdata.getConfiguration()).getRoot();
816 }
817 else
818 {
819
820 HierarchicalConfiguration hc = new HierarchicalConfiguration();
821 ConfigurationUtils.copy(cdata.getConfiguration(), hc);
822 return hc.getRoot();
823 }
824 }
825 }
826
827 /***
828 * A special implementation of Digester's <code>CallMethodRule</code> that
829 * is internally used for calling a file configuration's <code>load()</code>
830 * method. This class difers from its ancestor that it catches all occuring
831 * exceptions when the specified method is called. It then checks whether
832 * for the corresponding configuration the optional attribute is set. If
833 * this is the case, the exception will simply be ignored.
834 *
835 * @since 1.4
836 */
837 private static class CallOptionalMethodRule extends CallMethodRule
838 {
839 /*** A flag whether the optional attribute is set for this node. */
840 private boolean optional;
841
842 /***
843 * Creates a new instance of <code>CallOptionalMethodRule</code> and
844 * sets the name of the method to invoke.
845 *
846 * @param methodName the name of the method
847 */
848 public CallOptionalMethodRule(String methodName)
849 {
850 super(methodName);
851 }
852
853 /***
854 * Checks if the optional attribute is set.
855 *
856 * @param attrs the attributes
857 * @throws Exception if an error occurs
858 */
859 public void begin(Attributes attrs) throws Exception
860 {
861 optional = attrs.getValue(ATTR_OPTIONAL) != null
862 && PropertyConverter.toBoolean(
863 attrs.getValue(ATTR_OPTIONAL)).booleanValue();
864 super.begin(attrs);
865 }
866
867 /***
868 * Calls the method. If the optional attribute was set, occurring
869 * exceptions will be ignored.
870 *
871 * @throws Exception if an error occurs
872 */
873 public void end() throws Exception
874 {
875 try
876 {
877 super.end();
878 }
879 catch (Exception ex)
880 {
881 if (optional)
882 {
883 log.warn("Could not create optional configuration!", ex);
884 }
885 else
886 {
887 throw ex;
888 }
889 }
890 }
891 }
892 }