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