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 }