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