001    /*
002     * Copyright (c) 2011, Cloudera, Inc. All Rights Reserved.
003     *
004     * Cloudera, Inc. licenses this file to you under the Apache License,
005     * Version 2.0 (the "License"). You may not use this file except in
006     * compliance with the License. You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
011     * CONDITIONS OF ANY KIND, either express or implied. See the License for
012     * the specific language governing permissions and limitations under the
013     * License.
014     */
015    package com.cloudera.lib.util;
016    
017    import org.apache.hadoop.conf.Configuration;
018    import org.w3c.dom.DOMException;
019    import org.w3c.dom.Document;
020    import org.w3c.dom.Element;
021    import org.w3c.dom.Node;
022    import org.w3c.dom.NodeList;
023    import org.w3c.dom.Text;
024    import org.xml.sax.ErrorHandler;
025    import org.xml.sax.InputSource;
026    import org.xml.sax.SAXException;
027    import org.xml.sax.SAXParseException;
028    
029    import javax.xml.parsers.DocumentBuilder;
030    import javax.xml.parsers.DocumentBuilderFactory;
031    import javax.xml.parsers.ParserConfigurationException;
032    import java.io.ByteArrayOutputStream;
033    import java.io.IOException;
034    import java.io.InputStream;
035    import java.io.Reader;
036    import java.util.Map;
037    import java.util.Properties;
038    
039    /**
040     * Extends Hadoop Configuration providing a new constructor which reads an XML configuration from an InputStream.
041     * <p/>
042     * OConfiguration(InputStream is).
043     */
044    public class XConfiguration extends Configuration {
045    
046      /**
047       * Create an empty configuration.
048       * <p/>
049       * Default values are not loaded.
050       */
051      public XConfiguration() {
052        super(false);
053      }
054    
055      /**
056       * Create a configuration from an InputStream.
057       * <p/>
058       * ERROR canibalized from <code>Configuration.loadResource()</code>.
059       *
060       * @param is inputstream to read the configuration from.
061       *
062       * @throws IOException thrown if the configuration could not be read.
063       */
064      public XConfiguration(InputStream is) throws IOException {
065        this();
066        parse(is);
067      }
068    
069      /**
070       * Create a configuration from an Reader.
071       * <p/>
072       * ERROR canibalized from <code>Configuration.loadResource()</code>.
073       *
074       * @param reader reader to read the configuration from.
075       *
076       * @throws IOException thrown if the configuration could not be read.
077       */
078      public XConfiguration(Reader reader) throws IOException {
079        this();
080        Check.notNull(reader, "reader");
081        parse(reader);
082      }
083    
084      /**
085       * Create an configuration from a Properties instance.
086       *
087       * @param props Properties instance to get all properties from.
088       */
089      public XConfiguration(Properties props) {
090        this();
091        Check.notNull(props, "props)");
092        for (Map.Entry entry : props.entrySet()) {
093          set((String) entry.getKey(), (String) entry.getValue());
094        }
095    
096      }
097    
098      /**
099       * This is a stop gap fix for <link href="https://issues.apache.org/jira/browse/HADOOP-4416">HADOOP-4416</link>.
100       */
101      public Class<?> getClassByName(String name) throws ClassNotFoundException {
102        Check.notEmpty(name, "name");
103        return super.getClassByName(name.trim());
104      }
105    
106      /**
107       * Copy configuration key/value pairs from one configuration to another if a property exists in the target, it gets
108       * replaced.
109       *
110       * @param source source configuration.
111       * @param target target configuration.
112       */
113      public static void copy(Configuration source, Configuration target) {
114        Check.notNull(source, "source");
115        Check.notNull(target, "target");
116        for (Map.Entry<String, String> entry : source) {
117          target.set(entry.getKey(), entry.getValue());
118        }
119      }
120    
121      /**
122       * Injects configuration key/value pairs from one configuration to another if the key does not exist in the target
123       * configuration.
124       *
125       * @param source source configuration.
126       * @param target target configuration.
127       */
128      public static void injectDefaults(Configuration source, Configuration target) {
129        Check.notNull(source, "source");
130        Check.notNull(target, "target");
131        for (Map.Entry<String, String> entry : source) {
132          if (target.get(entry.getKey()) == null) {
133            target.set(entry.getKey(), entry.getValue());
134          }
135        }
136      }
137    
138      /**
139       * Returns a new XConfiguration with all values trimmed.
140       *
141       * @return a new XConfiguration with all values trimmed.
142       */
143      public XConfiguration trim() {
144        XConfiguration trimmed = new XConfiguration();
145        for (Map.Entry<String, String> entry : this) {
146          trimmed.set(entry.getKey(), entry.getValue().trim());
147        }
148        return trimmed;
149      }
150    
151      /**
152       * Returns a new XConfiguration instance with all inline values resolved.
153       *
154       * @return a new XConfiguration instance with all inline values resolved.
155       */
156      public XConfiguration resolve() {
157        XConfiguration resolved = new XConfiguration();
158        for (Map.Entry<String, String> entry : this) {
159          resolved.set(entry.getKey(), get(entry.getKey()));
160        }
161        return resolved;
162      }
163    
164      // Canibalized from Hadoop <code>Configuration.loadResource()</code>.
165      private void parse(InputStream is) throws IOException {
166        try {
167          DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
168          // ignore all comments inside the xml file
169          docBuilderFactory.setIgnoringComments(true);
170          DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
171          Document doc = builder.parse(is);
172          parseDocument(doc);
173        }
174        catch (SAXException e) {
175          throw new IOException(e);
176        }
177        catch (ParserConfigurationException e) {
178          throw new IOException(e);
179        }
180      }
181    
182      // Canibalized from Hadoop <code>Configuration.loadResource()</code>.
183      private void parse(Reader reader) throws IOException {
184        try {
185          DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
186          // ignore all comments inside the xml file
187          docBuilderFactory.setIgnoringComments(true);
188          DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();
189          builder.setErrorHandler(new ErrorHandler() {
190            @Override
191            public void warning(SAXParseException exception) throws SAXException {
192              throw exception;
193            }
194    
195            @Override
196            public void error(SAXParseException exception) throws SAXException {
197              throw exception;
198            }
199    
200            @Override
201            public void fatalError(SAXParseException exception) throws SAXException {
202              throw exception;
203            }
204          });
205          Document doc = builder.parse(new InputSource(reader));
206          parseDocument(doc);
207        }
208        catch (SAXException e) {
209          throw new IOException(e);
210        }
211        catch (ParserConfigurationException e) {
212          throw new IOException(e);
213        }
214      }
215    
216      // Canibalized from Hadoop <code>Configuration.loadResource()</code>.
217      private void parseDocument(Document doc) throws IOException {
218        try {
219          Element root = doc.getDocumentElement();
220          if (!"configuration".equals(root.getTagName())) {
221            throw new IOException("bad conf file: top-level element not <configuration>");
222          }
223          NodeList props = root.getChildNodes();
224          for (int i = 0; i < props.getLength(); i++) {
225            Node propNode = props.item(i);
226            if (!(propNode instanceof Element)) {
227              continue;
228            }
229            Element prop = (Element) propNode;
230            if (!"property".equals(prop.getTagName())) {
231              throw new IOException("bad conf file: element not <property>");
232            }
233            NodeList fields = prop.getChildNodes();
234            String attr = null;
235            String value = null;
236            for (int j = 0; j < fields.getLength(); j++) {
237              Node fieldNode = fields.item(j);
238              if (!(fieldNode instanceof Element)) {
239                continue;
240              }
241              Element field = (Element) fieldNode;
242              if ("name".equals(field.getTagName()) && field.hasChildNodes()) {
243                attr = ((Text) field.getFirstChild()).getData().trim();
244              }
245              if ("value".equals(field.getTagName()) && field.hasChildNodes()) {
246                value = ((Text) field.getFirstChild()).getData();
247              }
248            }
249    
250            if (attr != null && value != null) {
251              set(attr, value);
252            }
253          }
254    
255        }
256        catch (DOMException e) {
257          throw new IOException(e);
258        }
259      }
260    
261      /**
262       * Return a string with the configuration in XML format.
263       *
264       * @return a string with the configuration in XML format.
265       */
266      public String toXmlString() {
267        return toXmlString(true);
268      }
269    
270      public String toXmlString(boolean prolog) {
271        String xml;
272        try {
273          ByteArrayOutputStream baos = new ByteArrayOutputStream();
274          this.writeXml(baos);
275          baos.close();
276          xml = new String(baos.toByteArray());
277        }
278        catch (IOException ex) {
279          throw new RuntimeException("It should not happen, " + ex.getMessage(), ex);
280        }
281        if (!prolog) {
282          xml = xml.substring(xml.indexOf("<configuration>"));
283        }
284        return xml;
285      }
286    
287      /**
288       * Return a Properties instance with the configuration properties.
289       *
290       * @return a Properties instance with the configuration properties.
291       */
292      public Properties toProperties() {
293        Properties props = new Properties();
294        for (Map.Entry<String, String> entry : this) {
295          props.setProperty(entry.getKey(), entry.getValue());
296        }
297        return props;
298      }
299    
300    }