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 }