View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.commons.text.lookup;
19  
20  import java.io.InputStream;
21  import java.nio.file.Files;
22  import java.nio.file.Path;
23  import java.util.HashMap;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  import java.util.Objects;
27  
28  import javax.xml.XMLConstants;
29  import javax.xml.parsers.DocumentBuilderFactory;
30  import javax.xml.xpath.XPathFactory;
31  
32  import org.apache.commons.lang3.StringUtils;
33  import org.w3c.dom.Document;
34  
35  /**
36   * Looks up values in an XML document in the format {@code "DocumentPath:XPath"}.
37   * <p>
38   * For example:
39   * </p>
40   * <ul>
41   * <li>{@code "com/domain/document.xml:/path/to/node"}</li>
42   * </ul>
43   * <p>
44   * Secure processing is enabled by default and can be overridden with {@link StringLookupFactory#xmlStringLookup(Map, Path...)}.
45   * </p>
46   *
47   * @since 1.5
48   */
49  final class XmlStringLookup extends AbstractPathFencedLookup {
50  
51      /**
52       * The number of key parts.
53       */
54      private static final int KEY_PARTS_LEN = 2;
55  
56      /**
57       * Defines default XPath factory features.
58       */
59      static final Map<String, Boolean> DEFAULT_XPATH_FEATURES;
60  
61      /**
62       * Defines default XML factory features.
63       */
64      static final Map<String, Boolean> DEFAULT_XML_FEATURES;
65      static {
66          DEFAULT_XPATH_FEATURES = new HashMap<>(1);
67          DEFAULT_XPATH_FEATURES.put(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
68          DEFAULT_XML_FEATURES = new HashMap<>(1);
69          DEFAULT_XML_FEATURES.put(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
70      }
71  
72      /**
73       * Defines the singleton for this class with secure processing enabled by default.
74       * <p>
75       * Secure processing is enabled by default and can be overridden with {@link StringLookupFactory#xmlStringLookup(Map, Path...)}.
76       * </p>
77       */
78      static final XmlStringLookup INSTANCE = new XmlStringLookup(DEFAULT_XML_FEATURES, DEFAULT_XPATH_FEATURES, (Path[]) null);
79  
80      /**
81       * Defines XPath factory features.
82       */
83      private final Map<String, Boolean> xPathFactoryFeatures;
84  
85      /**
86       * Defines XML factory features.
87       */
88      private final Map<String, Boolean> xmlFactoryFeatures;
89  
90      /**
91       * Constructs a new instance.
92       *
93       * @param xmlFactoryFeatures   The {@link DocumentBuilderFactory} features to set.
94       * @param xPathFactoryFeatures The {@link XPathFactory} features to set.
95       * @see DocumentBuilderFactory#setFeature(String, boolean)
96       * @see XPathFactory#setFeature(String, boolean)
97       */
98      XmlStringLookup(final Map<String, Boolean> xmlFactoryFeatures, final Map<String, Boolean> xPathFactoryFeatures, final Path... fences) {
99          super(fences);
100         this.xmlFactoryFeatures = Objects.requireNonNull(xmlFactoryFeatures, "xmlFactoryFeatures");
101         this.xPathFactoryFeatures = Objects.requireNonNull(xPathFactoryFeatures, "xPathFfactoryFeatures");
102     }
103 
104     /**
105      * Looks up a value for the key in the format {@code "DocumentPath:XPath"}.
106      * <p>
107      * For example:
108      * </p>
109      * <ul>
110      * <li>{@code "com/domain/document.xml:/path/to/node"}</li>
111      * </ul>
112      * <p>
113      * Secure processing is enabled by default and can be overridden with {@link StringLookupFactory#xmlStringLookup(Map, Path...)}.
114      * </p>
115      *
116      * @param key the key to be looked up, may be null.
117      * @return The value associated with the key.
118      */
119     @Override
120     public String lookup(final String key) {
121         if (key == null) {
122             return null;
123         }
124         final String[] keys = key.split(SPLIT_STR);
125         final int keyLen = keys.length;
126         if (keyLen != KEY_PARTS_LEN) {
127             throw IllegalArgumentExceptions.format("Bad XML key format '%s'; the expected format is 'DocumentPath:XPath'.", key);
128         }
129         final String documentPath = keys[0];
130         final String xpath = StringUtils.substringAfterLast(key, SPLIT_CH);
131         final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
132         try {
133             for (final Entry<String, Boolean> p : xmlFactoryFeatures.entrySet()) {
134                 dbFactory.setFeature(p.getKey(), p.getValue());
135             }
136             try (InputStream inputStream = Files.newInputStream(getPath(documentPath))) {
137                 final Document doc = dbFactory.newDocumentBuilder().parse(inputStream);
138                 final XPathFactory xpFactory = XPathFactory.newInstance();
139                 for (final Entry<String, Boolean> p : xPathFactoryFeatures.entrySet()) {
140                     xpFactory.setFeature(p.getKey(), p.getValue());
141                 }
142                 return xpFactory.newXPath().evaluate(xpath, doc);
143             }
144         } catch (final Exception e) {
145             throw new IllegalArgumentException(e);
146         }
147     }
148 }