View Javadoc
1   /* ***************************************************************************
2    * Copyright (c) 2008 Brabenetz Harald, Austria.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   * 
16   *****************************************************************************/
17  package org.settings4j.config;
18  
19  import java.beans.PropertyDescriptor;
20  import java.io.IOException;
21  import java.lang.reflect.Constructor;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import javax.xml.parsers.DocumentBuilder;
31  import javax.xml.parsers.DocumentBuilderFactory;
32  import javax.xml.parsers.FactoryConfigurationError;
33  
34  import org.apache.commons.beanutils.PropertyUtils;
35  import org.apache.commons.lang3.BooleanUtils;
36  import org.apache.commons.lang3.StringUtils;
37  import org.settings4j.Connector;
38  import org.settings4j.ContentResolver;
39  import org.settings4j.Filter;
40  import org.settings4j.ObjectResolver;
41  import org.settings4j.Settings4jInstance;
42  import org.settings4j.Settings4jRepository;
43  import org.settings4j.connector.CachedConnectorWrapper;
44  import org.settings4j.connector.FilteredConnectorWrapper;
45  import org.settings4j.contentresolver.ClasspathContentResolver;
46  import org.settings4j.contentresolver.FilteredContentResolverWrapper;
47  import org.settings4j.objectresolver.AbstractObjectResolver;
48  import org.settings4j.objectresolver.FilteredObjectResolverWrapper;
49  import org.settings4j.settings.DefaultFilter;
50  import org.settings4j.util.ELConnectorWrapper;
51  import org.settings4j.util.ExpressionLanguageUtil;
52  import org.w3c.dom.Document;
53  import org.w3c.dom.Element;
54  import org.w3c.dom.NamedNodeMap;
55  import org.w3c.dom.Node;
56  import org.w3c.dom.NodeList;
57  import org.xml.sax.SAXException;
58  
59  /**
60   * @author Harald.Brabenetz
61   */
62  public class DOMConfigurator {
63  
64      /** General Logger for this Class. */
65      private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(DOMConfigurator.class);
66  
67      private static final String CONFIGURATION_TAG = "settings4j:configuration";
68  
69      private static final String CONNECTOR_TAG = "connector";
70  
71      private static final String CONNECTOR_REF_TAG = "connector-ref";
72  
73      private static final String OBJECT_RESOLVER_TAG = "objectResolver";
74  
75      private static final String OBJECT_RESOLVER_REF_TAG = "objectResolver-ref";
76  
77      private static final String CONTENT_RESOLVER_TAG = "contentResolver";
78  
79      private static final String CONTENT_RESOLVER_REF_TAG = "contentResolver-ref";
80  
81      private static final String MAPPING_TAG = "mapping";
82  
83      private static final String FILTER_TAG = "filter";
84  
85      private static final String EXCLUDE_TAG = "exclude";
86  
87      private static final String INCLUDE_TAG = "include";
88  
89      private static final String ENTRY_TAG = "entry";
90  
91      private static final String ENTRY_KEY_ATTR = "key";
92  
93      private static final String ENTRY_REFKEY_ATTR = "ref-key";
94  
95      private static final String PARAM_TAG = "param";
96  
97      private static final String NAME_ATTR = "name";
98  
99      private static final String CLASS_ATTR = "class";
100 
101     private static final String PATTERN_ATTR = "pattern";
102 
103     private static final String CACHED_ATTR = "cached";
104 
105     private static final String VALUE_ATTR = "value";
106 
107     private static final String REF_ATTR = "ref";
108 
109     private static final String DOCUMENT_BUILDER_FACTORY_KEY = "javax.xml.parsers.DocumentBuilderFactory";
110 
111 
112     // key: ConnectorName, value: Connector
113     private final Map<String, Connector> connectorBag;
114     // key: contentResolver-Name, value: ContentResolver
115     private final Map<String, ContentResolver> contentResolverBag;
116     // key: objectResolver-Name, value: ObjectResolver
117     private final Map<String, ObjectResolver> objectResolverBag;
118 
119     private final Settings4jRepository repository;
120 
121     private final Map<String, Object> expressionAttributes = new HashMap<String, Object>();
122 
123     /**
124      * Configure the given Settings4jRepository with an XMl-configuration (see settings4j.dtd).
125      * @param repository The Repository to configure.
126      */
127     public DOMConfigurator(final Settings4jRepository repository) {
128         super();
129         this.repository = repository;
130         this.connectorBag = new HashMap<String, Connector>();
131         this.contentResolverBag = new HashMap<String, ContentResolver>();
132         this.objectResolverBag = new HashMap<String, ObjectResolver>();
133     }
134 
135     /**
136      * Sets a parameter based from configuration file content.
137      * 
138      * @param elem param element, may not be null.
139      * @param propSetter property setter, may not be null.
140      * @param props properties
141      */
142     private void setParameter(final Element elem, final Object bean, final Connector[] connectors) {
143         final String name = elem.getAttribute(NAME_ATTR);
144         final String valueStr = elem.getAttribute(VALUE_ATTR);
145         try {
146             final PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(bean, name);
147             final Method setter = PropertyUtils.getWriteMethod(propertyDescriptor);
148             Object value;
149             if (connectors != null) {
150                 value = subst(valueStr, connectors, setter.getParameterTypes()[0]);
151             } else {
152                 value = subst(valueStr, null, setter.getParameterTypes()[0]);
153             }
154             PropertyUtils.setProperty(bean, name, value);
155         } catch (final IllegalAccessException e) {
156             LOG.warn("Cannnot set Property: " + name, e);
157         } catch (final InvocationTargetException e) {
158             LOG.warn("Cannnot set Property: " + name, e);
159         } catch (final NoSuchMethodException e) {
160             LOG.warn("Cannnot set Property: " + name, e);
161         }
162     }
163 
164     /**
165      * A static version of {@link #doConfigure(URL)}.
166      * 
167      * @param url The location of the configuration file.
168      * @param repository the Repository to configure.
169      * @throws FactoryConfigurationError if {@link DocumentBuilderFactory#newInstance()} throws an exception.
170      */
171     public static void configure(//
172             final URL url, final Settings4jRepository repository) throws FactoryConfigurationError {
173         new DOMConfigurator(repository).doConfigure(url);
174     }
175 
176     /**
177      * @param url The location of the configuration file.
178      */
179     public void doConfigure(final URL url) {
180         final ParseAction action = new ParseAction() {
181 
182             public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
183                 return parser.parse(url.toString());
184             }
185 
186             @Override
187             public String toString() {
188                 return "url [" + url.toString() + "]";
189             }
190         };
191         doConfigure(action);
192     }
193 
194     private void doConfigure(final ParseAction action) throws FactoryConfigurationError {
195         DocumentBuilderFactory dbf = null;
196         try {
197             LOG.debug("System property is: {}", System.getProperty(DOCUMENT_BUILDER_FACTORY_KEY));
198             dbf = DocumentBuilderFactory.newInstance();
199             LOG.debug("Standard DocumentBuilderFactory search succeded.");
200             LOG.debug("DocumentBuilderFactory is: {}", dbf.getClass().getName());
201         } catch (final FactoryConfigurationError fce) {
202             final Exception e = fce.getException();
203             LOG.debug("Could not instantiate a DocumentBuilderFactory.", e);
204             throw fce;
205         }
206 
207         try {
208             dbf.setValidating(true);
209 
210             final DocumentBuilder docBuilder = dbf.newDocumentBuilder();
211 
212             docBuilder.setErrorHandler(new SAXErrorHandler());
213             docBuilder.setEntityResolver(new Settings4jEntityResolver());
214 
215             final Document doc = action.parse(docBuilder);
216             parse(doc.getDocumentElement());
217         } catch (final Exception e) {
218             // I know this is miserable...
219             LOG.error("Could not parse " + action.toString() + ".", e);
220         }
221     }
222 
223     /**
224      * Used internally to configure the settings4j framework by parsing a DOM tree of XML elements based on <a
225      * href="doc-files/settings4j.dtd">settings4j.dtd</a>.
226      * 
227      * @param element The XML {@link #CONFIGURATION_TAG} Element.
228      */
229     protected void parse(final Element element) {
230 
231         final String rootElementName = element.getTagName();
232 
233         if (!rootElementName.equals(CONFIGURATION_TAG)) {
234             LOG.error("DOM element is - not a <{}> element.", CONFIGURATION_TAG);
235             return;
236         }
237 
238         final Settings4jInstance root = this.repository.getSettings();
239         // settings configuration needs to be atomic
240         synchronized (root) {
241             parseChildrenOfSettingsElement(element, root);
242         }
243     }
244 
245     /**
246      * Used internally to parse an Filter element.
247      * 
248      * @param filterElement the XML-Element for Filter.
249      * @return the Filter-Object
250      */
251     protected Filter parseFilter(final Element filterElement) {
252 
253         Filter filter;
254 
255 
256         String className = filterElement.getAttribute(CLASS_ATTR);
257         if (StringUtils.isEmpty(className)) {
258             className = DefaultFilter.class.getName();
259         }
260 
261         LOG.debug("Desired Connector class: [{}]", className);
262         try {
263             final Class clazz = loadClass(className);
264             final Constructor constructor = clazz.getConstructor();
265             filter = (Filter) constructor.newInstance();
266         } catch (final Exception oops) {
267             LOG.error("Could not retrieve connector [filter: " + className + "]. Reported error follows.", oops);
268             return null;
269         }
270 
271         final NodeList children = filterElement.getChildNodes();
272         final int length = children.getLength();
273 
274         for (int loop = 0; loop < length; loop++) {
275             final Node currentNode = children.item(loop);
276 
277             if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
278                 final Element currentElement = (Element) currentNode;
279                 final String tagName = currentElement.getTagName();
280 
281                 if (tagName.equals(INCLUDE_TAG)) {
282                     final Element includeTag = (Element) currentNode;
283                     final String patteren = includeTag.getAttribute(PATTERN_ATTR);
284                     filter.addInclude(patteren);
285 
286                 } else if (tagName.equals(EXCLUDE_TAG)) {
287                     final Element excludeTag = (Element) currentNode;
288                     final String patteren = excludeTag.getAttribute(PATTERN_ATTR);
289                     filter.addExclude(patteren);
290                 } else {
291                     quietParseUnrecognizedElement(filter, currentElement);
292                 }
293             }
294         }
295 
296         return filter;
297     }
298 
299     /**
300      * Used internally to parse an connector element.
301      * 
302      * @param connectorElement The XML Connector Element
303      * @return the Connector Object.
304      */
305     protected Connector parseConnector(final Element connectorElement) {
306         final String connectorName = connectorElement.getAttribute(NAME_ATTR);
307 
308         Connector connector;
309 
310         final String className = connectorElement.getAttribute(CLASS_ATTR);
311 
312 
313         LOG.debug("Desired Connector class: [{}]", className);
314         try {
315             final Class clazz = loadClass(className);
316             final Constructor constructor = clazz.getConstructor();
317             connector = (Connector) constructor.newInstance();
318         } catch (final Exception oops) {
319             LOG.error("Could not retrieve connector [" + connectorName + "]. Reported error follows.", oops);
320             return null;
321         }
322 
323         connector.setName(connectorName);
324 
325         final Connector[] subConnectors = getConnectors(connectorElement);
326         for (Connector subConnector : subConnectors) {
327             connector.addConnector(subConnector);
328         }
329 
330         Filter filter = null;
331         // Setting up a connector needs to be an atomic operation, in order
332         // to protect potential setXXX operations while connector
333         // configuration is in progress.
334         synchronized (connector) {
335 
336             final NodeList children = connectorElement.getChildNodes();
337             final int length = children.getLength();
338 
339             for (int loop = 0; loop < length; loop++) {
340                 final Node currentNode = children.item(loop);
341 
342                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
343                     final Element currentElement = (Element) currentNode;
344                     final String tagName = currentElement.getTagName();
345 
346                     if (tagName.equals(CONNECTOR_REF_TAG)) {
347                         LOG.debug("{} is only parsed for the RegularExpression Context " //
348                             + "or init-Method. See org.settings4j.Connector.addConnector(Connector) Javadoc.", //
349                             CONNECTOR_REF_TAG);
350                     } else if (tagName.equals(CONTENT_RESOLVER_REF_TAG)) {
351                         final Element contentResolverRef = (Element) currentNode;
352                         final ContentResolver contentResolver = findContentResolverByReference(contentResolverRef);
353                         connector.setContentResolver(contentResolver);
354 
355                     } else if (tagName.equals(OBJECT_RESOLVER_REF_TAG)) {
356                         final Element objectResolverRef = (Element) currentNode;
357                         final ObjectResolver objectResolver = findObjectResolverByReference(objectResolverRef);
358                         connector.setObjectResolver(objectResolver);
359 
360                     } else if (tagName.equals(FILTER_TAG)) {
361                         final Element filterElement = (Element) currentNode;
362                         filter = parseFilter(filterElement);
363                     } else if (tagName.equals(PARAM_TAG)) {
364                         setParameter(currentElement, connector, subConnectors);
365                     } else {
366                         quietParseUnrecognizedElement(connector, currentElement);
367                     }
368                 }
369             }
370 
371             final Boolean cached = (Boolean) subst(connectorElement.getAttribute(CACHED_ATTR), subConnectors,
372                 Boolean.class);
373             if (cached != null && cached.booleanValue()) {
374                 connector = new CachedConnectorWrapper(connector);
375             }
376 
377             if (filter != null) {
378                 connector = new FilteredConnectorWrapper(connector, filter);
379             }
380 
381             // initial the connector
382             connector.init();
383         }
384         return connector;
385     }
386 
387     /**
388      * Only logs out unrecognized Elements.
389      * 
390      * @param instance instance, may be null.
391      * @param element element, may not be null.
392      */
393     private static void quietParseUnrecognizedElement(final Object instance, final Element element) {
394         String elementName = "UNKNOWN";
395         String instanceClassName = "UNKNOWN";
396 
397         try {
398             elementName = element.getNodeName();
399             instanceClassName = instance.getClass().getName();
400         } catch (final Exception e) {
401             LOG.warn("Error in quietParseUnrecognizedElement(): {}", e.getMessage());
402             LOG.debug(e.getMessage(), e);
403         }
404         LOG.warn("Unrecognized Element will be ignored: {} for Instance: {}", elementName, instanceClassName);
405     }
406 
407     /**
408      * Return all referenced connectors from Child-Nodes {@link #CONNECTOR_REF_TAG}.
409      * 
410      * @param connectorsElement The XML Connectors Element
411      * @return the Connectors Objects as Array.
412      */
413     protected Connector[] getConnectors(final Element connectorsElement) {
414         final List<Connector> connectors = new ArrayList<Connector>();
415 
416         final NodeList children = connectorsElement.getChildNodes();
417         final int length = children.getLength();
418 
419         for (int loop = 0; loop < length; loop++) {
420             final Node currentNode = children.item(loop);
421 
422             if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
423                 final Element currentElement = (Element) currentNode;
424                 final String tagName = currentElement.getTagName();
425 
426                 if (tagName.equals(CONNECTOR_REF_TAG)) {
427                     final Element connectorRef = (Element) currentNode;
428                     final Connector connector = findConnectorByReference(connectorRef);
429                     connectors.add(connector);
430                 }
431             }
432         }
433 
434         return connectors.toArray(new Connector[connectors.size()]);
435     }
436 
437 
438     /**
439      * Used internally to parse the children of a settings element.
440      * 
441      * @param settingsElement The XML Settings Element
442      * @param settings _The Settings Object do apply the values.
443      */
444     protected void parseChildrenOfSettingsElement(final Element settingsElement, final Settings4jInstance settings) {
445 
446         Node currentNode = null;
447         Element currentElement = null;
448         String tagName = null;
449 
450         // Remove all existing appenders from settings. They will be
451         // reconstructed if need be.
452         settings.removeAllConnectors();
453 
454         // first parse Connectors (are needed to parse param Tags
455         final NodeList connectorElements = settingsElement.getElementsByTagName(CONNECTOR_TAG);
456         int length = connectorElements.getLength();
457         for (int i = 0; i < length; i++) {
458             currentNode = connectorElements.item(i);
459             currentElement = (Element) currentNode;
460 
461             final Connector connector = parseConnector(currentElement);
462             if (connector != null) {
463                 this.connectorBag.put(connector.getName(), connector);
464                 settings.addConnector(connector);
465             }
466         }
467 
468         final List<Connector> list = settings.getConnectors();
469         final Connector[] connectors = list.toArray(new Connector[list.size()]);
470 
471         final NodeList children = settingsElement.getChildNodes();
472 
473         // Now parse other Tags like PARAM or MAPPING
474         length = children.getLength();
475         for (int i = 0; i < length; i++) {
476             currentNode = children.item(i);
477             if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
478                 currentElement = (Element) currentNode;
479                 tagName = currentElement.getTagName();
480 
481                 if (tagName.equals(MAPPING_TAG)) {
482                     final Map mapping = parseMapping(currentElement);
483                     if (mapping != null) {
484                         settings.setMapping(mapping);
485                     }
486                 } else if (tagName.equals(PARAM_TAG)) {
487                     setParameter(currentElement, settings, connectors);
488                 } else if (tagName.equals(CONNECTOR_TAG)) {
489                     LOG.trace("CONNECTOR_TAG already parsed");
490                 } else if (tagName.equals(CONTENT_RESOLVER_TAG)) {
491                     LOG.trace("CONTENT_RESOLVER_TAG will be parsed on the maned");
492                 } else if (tagName.equals(OBJECT_RESOLVER_TAG)) {
493                     LOG.trace("OBJECT_RESOLVER_TAG will be parsed on the maned");
494                 } else {
495                     quietParseUnrecognizedElement(settings, currentElement);
496                 }
497             }
498         }
499     }
500 
501 
502     /**
503      * Used internally to parse connectors by IDREF name.
504      * 
505      * @param doc The whole XML configuration.
506      * @param connectorName The Connector-Name, to search for.
507      * @return the Connector instance.
508      */
509     protected Connector findConnectorByName(final Document doc, final String connectorName) {
510         Connector connector = this.connectorBag.get(connectorName);
511 
512         if (connector != null) {
513             return connector;
514         }
515         // else
516         final Element element = getElementByNameAttr(doc, connectorName, "connector");
517 
518         if (element == null) {
519             LOG.error("No connector named [{}] could be found.", connectorName);
520             return null;
521         }
522         // else
523         connector = parseConnector(element);
524         this.connectorBag.put(connectorName, connector);
525         return connector;
526     }
527 
528     /**
529      * Used internally to parse connectors by IDREF element.
530      * 
531      * @param connectorRef The Element with the {@link #REF_ATTR} - Attribute.
532      * @return the Connector instance.
533      */
534     protected Connector findConnectorByReference(final Element connectorRef) {
535         final String connectorName = connectorRef.getAttribute(REF_ATTR);
536         final Document doc = connectorRef.getOwnerDocument();
537         return findConnectorByName(doc, connectorName);
538     }
539 
540 
541     /**
542      * Used internally to parse an objectResolver element.
543      * 
544      * @param objectResolverElement The XML Object resolver Element.
545      * @return The ObjectResolver instance.
546      */
547     protected ObjectResolver parseObjectResolver(final Element objectResolverElement) {
548         final String objectResolverName = objectResolverElement.getAttribute(NAME_ATTR);
549 
550         ObjectResolver objectResolver;
551 
552         final String className = objectResolverElement.getAttribute(CLASS_ATTR);
553 
554 
555         LOG.debug("Desired ObjectResolver class: [{}]", className);
556         try {
557             final Class clazz = loadClass(className);
558             final Constructor constructor = clazz.getConstructor();
559             objectResolver = (ObjectResolver) constructor.newInstance();
560         } catch (final Exception oops) {
561             LOG.error("Could not retrieve objectResolver [" + objectResolverName + "]. Reported error follows.", oops);
562             return null;
563         } catch (final NoClassDefFoundError e) {
564             LOG.warn("The ObjectResolver '" + objectResolverName
565                 + "' cannot be created. There are not all required Libraries inside the Classpath: " + e.getMessage(),
566                 e);
567             return null;
568         }
569 
570         // get connectors for ExpressionLanguage validation
571         final Connector[] connectors = getConnectors(objectResolverElement);
572 
573         Filter filter = null;
574         // Setting up a objectResolver needs to be an atomic operation, in order
575         // to protect potential settings operations while settings
576         // configuration is in progress.
577         synchronized (objectResolver) {
578 
579             final NodeList children = objectResolverElement.getChildNodes();
580             final int length = children.getLength();
581 
582             for (int loop = 0; loop < length; loop++) {
583                 final Node currentNode = children.item(loop);
584 
585                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
586                     final Element currentElement = (Element) currentNode;
587                     final String tagName = currentElement.getTagName();
588 
589 
590                     if (tagName.equals(CONNECTOR_REF_TAG)) {
591                         LOG.debug("{} is only parsed for the RegularExpression Context. " //
592                             + "See org.settings4j.Connector.addConnector(Connector) Javadoc.", //
593                             CONNECTOR_REF_TAG);
594                     } else if (tagName.equals(OBJECT_RESOLVER_REF_TAG)) {
595                         final Element objectResolverRef = (Element) currentNode;
596                         final ObjectResolver subObjectResolver = findObjectResolverByReference(objectResolverRef);
597                         objectResolver.addObjectResolver(subObjectResolver);
598 
599                     } else if (tagName.equals(PARAM_TAG)) {
600                         setParameter(currentElement, objectResolver, connectors);
601                     } else if (tagName.equals(FILTER_TAG)) {
602                         final Element filterElement = (Element) currentNode;
603                         filter = parseFilter(filterElement);
604                     } else {
605                         quietParseUnrecognizedElement(objectResolver, currentElement);
606                     }
607                 }
608             }
609 
610             String isCachedValue = objectResolverElement.getAttribute(CACHED_ATTR);
611             final Boolean isCached = (Boolean) subst(isCachedValue, null, Boolean.class);
612             if (BooleanUtils.isTrue(isCached)) {
613                 if (objectResolver instanceof AbstractObjectResolver) {
614                     ((AbstractObjectResolver) objectResolver).setCached(isCached.booleanValue());
615                 } else {
616                     LOG.warn("Only AbstractObjectResolver can use the attribute cached=\"true\" ");
617                     // TODO hbrabenetz 21.05.2008 : extract setCached into seperate Interface.
618                 }
619             }
620 
621             if (filter != null) {
622                 objectResolver = new FilteredObjectResolverWrapper(objectResolver, filter);
623             }
624         }
625 
626         return objectResolver;
627     }
628 
629 
630     /**
631      * Used internally to parse an mapping element.
632      * 
633      * @param mappingElement The XML Mapping Element.
634      * @return the Map instance (Key = String; Value = String).
635      */
636     @SuppressWarnings("unchecked")
637     protected Map parseMapping(final Element mappingElement) {
638         final String mappingName = mappingElement.getAttribute(NAME_ATTR);
639 
640         Map<String, String> mapping;
641 
642         String className = mappingElement.getAttribute(CLASS_ATTR);
643         if (StringUtils.isEmpty(className)) {
644             className = "java.util.HashMap";
645         }
646 
647         LOG.debug("Desired Map class: [{}]", className);
648         try {
649             final Class clazz = loadClass(className);
650             final Constructor constructor = clazz.getConstructor();
651             mapping = (Map<String, String>) constructor.newInstance();
652         } catch (final Exception oops) {
653             LOG.error("Could not retrieve mapping [" + mappingName + "]. Reported error follows.", oops);
654             return null;
655         } catch (final NoClassDefFoundError e) {
656             LOG.warn("The Mapping '" + mappingName
657                 + "' cannot be created. There are not all required Libraries inside the Classpath: " + e.getMessage(),
658                 e);
659             return null;
660         }
661 
662         // Setting up a mapping needs to be an atomic operation, in order
663         // to protect potential settings operations while settings
664         // configuration is in progress.
665         synchronized (mapping) {
666 
667             final NodeList children = mappingElement.getChildNodes();
668             final int length = children.getLength();
669 
670             for (int loop = 0; loop < length; loop++) {
671                 final Node currentNode = children.item(loop);
672 
673                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
674                     final Element currentElement = (Element) currentNode;
675                     final String tagName = currentElement.getTagName();
676 
677                     if (tagName.equals(ENTRY_TAG)) {
678                         final String key = currentElement.getAttribute(ENTRY_KEY_ATTR);
679                         final String keyRef = currentElement.getAttribute(ENTRY_REFKEY_ATTR);
680                         mapping.put(key, keyRef);
681                     } else {
682                         quietParseUnrecognizedElement(mapping, currentElement);
683                     }
684                 }
685             }
686         }
687 
688         return mapping;
689     }
690 
691 
692     /**
693      * Used internally to parse an contentResolver element.
694      * 
695      * @param contentResolverElement The ContentResolver Element.
696      * @return the ContentResolver instance.
697      */
698     protected ContentResolver parseContentResolver(final Element contentResolverElement) {
699         final String contentResolverName = contentResolverElement.getAttribute(NAME_ATTR);
700 
701         ContentResolver contentResolver;
702 
703         final String className = contentResolverElement.getAttribute(CLASS_ATTR);
704 
705 
706         LOG.debug("Desired ContentResolver class: [{}]", className);
707         try {
708             final Class clazz = loadClass(className);
709             final Constructor constructor = clazz.getConstructor();
710             contentResolver = (ContentResolver) constructor.newInstance();
711         } catch (final Exception e) {
712             LOG.error("Could not retrieve contentResolver [" + contentResolverName + "]. " //
713                 + "Reported error follows.", e);
714             return null;
715         } catch (final NoClassDefFoundError e) {
716             LOG.warn("The ContentResolver '" + contentResolverName + "' cannot be created. " //
717                 + "There are not all required Libraries inside the Classpath: " + e.getMessage(), e);
718             return null;
719         }
720 
721         // get connectors for ExpressionLanguage validation
722         final Connector[] connectors = getConnectors(contentResolverElement);
723 
724         Filter filter = null;
725         // Setting up a contentResolver needs to be an atomic operation, in order
726         // to protect potential settings operations while settings
727         // configuration is in progress.
728         synchronized (contentResolver) {
729 
730             final NodeList children = contentResolverElement.getChildNodes();
731             final int length = children.getLength();
732 
733             for (int loop = 0; loop < length; loop++) {
734                 final Node currentNode = children.item(loop);
735 
736                 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
737                     final Element currentElement = (Element) currentNode;
738                     final String tagName = currentElement.getTagName();
739 
740                     if (tagName.equals(CONNECTOR_REF_TAG)) {
741                         LOG.debug("{} is only parsed for the RegularExpression Context. " //
742                             + "See org.settings4j.Connector.addConnector(Connector) Javadoc.", //
743                             CONNECTOR_REF_TAG);
744                     } else if (tagName.equals(CONTENT_RESOLVER_REF_TAG)) {
745                         final Element contentResolverRef = (Element) currentNode;
746                         final ContentResolver subContentResolver = findContentResolverByReference(contentResolverRef);
747                         contentResolver.addContentResolver(subContentResolver);
748 
749                     } else if (tagName.equals(PARAM_TAG)) {
750                         setParameter(currentElement, contentResolver, connectors);
751                     } else if (tagName.equals(FILTER_TAG)) {
752                         final Element filterElement = (Element) currentNode;
753                         filter = parseFilter(filterElement);
754                     } else {
755                         quietParseUnrecognizedElement(contentResolver, currentElement);
756                     }
757                 }
758             }
759 
760             if (filter != null) {
761                 contentResolver = new FilteredContentResolverWrapper(contentResolver, filter);
762             }
763         }
764 
765         return contentResolver;
766     }
767 
768     /**
769      * Used internally to parse contentResolvers by IDREF name.
770      * 
771      * @param doc XML Document of the whole settings4j.xml.
772      * @param contentResolverName the contentResolver Name to search for.
773      * @return the ContentResolver instance.
774      */
775     protected ContentResolver findContentResolverByName(final Document doc, final String contentResolverName) {
776         ContentResolver contentResolver = this.contentResolverBag.get(contentResolverName);
777 
778         if (contentResolver != null) {
779             return contentResolver;
780         }
781         // else
782         final Element element = getElementByNameAttr(doc, contentResolverName, CONTENT_RESOLVER_TAG);
783 
784         if (element == null) {
785             LOG.error("No contentResolver named [{}] could be found.", contentResolverName);
786             return null;
787         }
788         // else
789         contentResolver = parseContentResolver(element);
790         this.contentResolverBag.put(contentResolverName, contentResolver);
791         return contentResolver;
792     }
793 
794 
795     /**
796      * Used internally to parse objectResolvers by IDREF name.
797      * 
798      * @param doc XML Document of the whole settings4j.xml.
799      * @param objectResolverName the ObjectResolver Name to search for.
800      * @return the ObjectResolver instance.
801      */
802     protected ObjectResolver findObjectResolverByName(final Document doc, final String objectResolverName) {
803         ObjectResolver objectResolver = this.objectResolverBag.get(objectResolverName);
804 
805         if (objectResolver != null) {
806             return objectResolver;
807         }
808         // else
809         final Element element = getElementByNameAttr(doc, objectResolverName, OBJECT_RESOLVER_TAG);
810     
811         if (element == null) {
812             LOG.error("No objectResolver named [{}] could be found.", objectResolverName);
813             return null;
814         }
815         // else
816         objectResolver = parseObjectResolver(element);
817         this.objectResolverBag.put(objectResolverName, objectResolver);
818         return objectResolver;
819     }
820 
821     /**
822      * Used internally to parse objectResolvers by IDREF element.
823      * 
824      * @param objectResolverRef The Element with the {@link #REF_ATTR}.
825      * @return the ObjectResolver instance.
826      */
827     protected ObjectResolver findObjectResolverByReference(final Element objectResolverRef) {
828         final String objectResolverName = objectResolverRef.getAttribute(REF_ATTR);
829         final Document doc = objectResolverRef.getOwnerDocument();
830         return findObjectResolverByName(doc, objectResolverName);
831     }
832 
833 
834     /**
835      * Used internally to parse contentResolvers by IDREF element.
836      * 
837      * @param contentResolverRef The Element with the {@link #REF_ATTR}.
838      * @return the ContentResolver instance.
839      */
840     protected ContentResolver findContentResolverByReference(final Element contentResolverRef) {
841         final String contentResolverName = contentResolverRef.getAttribute(REF_ATTR);
842         final Document doc = contentResolverRef.getOwnerDocument();
843         return findContentResolverByName(doc, contentResolverName);
844     }
845 
846 
847     private static Element getElementByNameAttr(final Document doc, final String nameAttrValue,
848             final String elementTagName) {
849         // Doesn't work on DOM Level 1 :
850         // Element element = doc.getElementById(nameAttrValue);
851 
852         // Endre's hack:
853         Element element = null;
854         final NodeList list = doc.getElementsByTagName(elementTagName);
855         for (int t = 0; t < list.getLength(); t++) {
856             final Node node = list.item(t);
857             final NamedNodeMap map = node.getAttributes();
858             final Node attrNode = map.getNamedItem("name");
859             if (nameAttrValue.equals(attrNode.getNodeValue())) {
860                 element = (Element) node;
861                 break;
862             }
863         }
864         // Hack finished.
865         return element;
866     }
867 
868 
869     /**
870      * @author Harald.Brabenetz
871      */
872     private interface ParseAction {
873         Document parse(final DocumentBuilder parser) throws SAXException, IOException;
874     }
875 
876     /**
877      * This function will replace expressions like ${connectors.string['']}.
878      * 
879      * @param value The value or Expression.
880      * @param connectors required for validating Expression like ${connectors.string['']}
881      * @return the String-Value of the given value or Expression
882      */
883     protected String subst(final String value, final Connector[] connectors) {
884         return (String) subst(value, connectors, String.class);
885     }
886 
887     /**
888      * This function will replace expressions like ${connectors.object['']} or simply ${true}.
889      * 
890      * @param value The value or Expression.
891      * @param clazz The expected {@link Class}.
892      * @param connectors required for validating Expression like ${connectors.string['']}
893      * @return the Object-Value of the given value or Expression.
894      */
895     protected Object subst(final String value, final Connector[] connectors, final Class clazz) {
896         if (StringUtils.isEmpty(value)) {
897             return null;
898         }
899 
900         if (value.indexOf("${") >= 0) {
901             try {
902                 final Map<String, Object> context = new HashMap<String, Object>(this.expressionAttributes);
903                 if (connectors != null) {
904                     // Expression like ${connectors.object['...']} or ${connectors.string['...']}
905                     context.put("connectors", new ELConnectorWrapper(connectors));
906 
907                     // Expression like ${connector.FsConnector.object['...']} or
908                     // ${connector.ClassPathConnector.string['...']}
909                     final Map<String, ELConnectorWrapper> connectorMap = new HashMap<String, ELConnectorWrapper>();
910                     for (Connector connector : connectors) {
911                         connectorMap.put(connector.getName(), new ELConnectorWrapper(new Connector[] {connector}));
912                     }
913                     context.put("connector", connectorMap);
914                 }
915                 context.put("env", System.getenv());
916                 final Object result = ExpressionLanguageUtil.evaluateExpressionLanguage(value, context, clazz);
917                 return result;
918             } catch (final Exception e) {
919                 LOG.error(e.getMessage(), e);
920                 return null;
921             }
922         }
923         // else 
924         if (clazz.equals(String.class)) {
925             return value.trim();
926         } else if (clazz.equals(Boolean.class)) {
927             return BooleanUtils.toBooleanObject(value.trim());
928         } else {
929             throw new UnsupportedOperationException("The following Type is not supported now: " + clazz
930                 + "; found value: " + value);
931         }
932     }
933 
934     /**
935      * Add a ExpressionAttribute like ('ContextPath', 'myApp').
936      * <p>
937      * A settings4j.xml Value like value="c:/settings/${contextPath}" will be evaluated as 
938      * "c:/settings/myApp".
939      * 
940      * @param key The Key as String.
941      * @param value The value as Object.
942      */
943     public void addExpressionAttribute(final String key, final Object value) {
944         this.expressionAttributes.put(key, value);
945     }
946 
947     private static Class loadClass(final String clazz) throws ClassNotFoundException {
948         return ClasspathContentResolver.getClassLoader().loadClass(clazz);
949     }
950 }