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.ByteArrayOutputStream; 034 import java.util.Map; 035 import java.util.Properties; 036 import java.util.regex.Matcher; 037 import java.util.regex.Pattern; 038 039 /** 040 * Extends Hadoop Configuration providing a new constructor which reads an XML configuration from an InputStream. <p/> 041 * OConfiguration(InputStream is). 042 */ 043 public class XConfiguration extends Configuration { 044 045 /** 046 * Create an empty configuration. <p/> Default values are not loaded. 047 */ 048 public XConfiguration() { 049 super(false); 050 } 051 052 /** 053 * Create a configuration from an InputStream. <p/> Code canibalized from <code>Configuration.loadResource()</code>. 054 * 055 * @param is inputstream to read the configuration from. 056 * @throws IOException thrown if the configuration could not be read. 057 */ 058 public XConfiguration(InputStream is) throws IOException { 059 this(); 060 parse(is); 061 } 062 063 /** 064 * Create a configuration from an Reader. <p/> Code canibalized from <code>Configuration.loadResource()</code>. 065 * 066 * @param reader reader to read the configuration from. 067 * @throws IOException thrown if the configuration could not be read. 068 */ 069 public XConfiguration(Reader reader) throws IOException { 070 this(); 071 parse(reader); 072 } 073 074 /** 075 * Create an configuration from a Properties instance. 076 * 077 * @param props Properties instance to get all properties from. 078 */ 079 public XConfiguration(Properties props) { 080 this(); 081 for (Map.Entry entry : props.entrySet()) { 082 set((String) entry.getKey(), (String) entry.getValue()); 083 } 084 085 } 086 087 /** 088 * Return a Properties instance with the configuration properties. 089 * 090 * @return a Properties instance with the configuration properties. 091 */ 092 public Properties toProperties() { 093 Properties props = new Properties(); 094 for (Map.Entry<String, String> entry : this) { 095 props.setProperty(entry.getKey(), entry.getValue()); 096 } 097 return props; 098 } 099 100 // overriding get() & substitueVars from Configuration to honor defined variables 101 // over system properties 102 //wee need this because substituteVars() is a private method and does not behave like virtual 103 //in Configuration 104 /** 105 * Get the value of the <code>name</code> property, <code>null</code> if 106 * no such property exists. 107 * 108 * Values are processed for <a href="#VariableExpansion">variable expansion</a> 109 * before being returned. 110 * 111 * @param name the property name. 112 * @return the value of the <code>name</code> property, 113 * or null if no such property exists. 114 */ 115 @Override 116 public String get(String name) { 117 return substituteVars(getRaw(name)); 118 } 119 120 /** 121 * Get the value of the <code>name</code> property. If no such property 122 * exists, then <code>defaultValue</code> is returned. 123 * 124 * @param name property name. 125 * @param defaultValue default value. 126 * @return property value, or <code>defaultValue</code> if the property 127 * doesn't exist. 128 */ 129 @Override 130 public String get(String name, String defaultValue) { 131 String value = getRaw(name); 132 if (value == null) { 133 value = defaultValue; 134 } 135 else { 136 value = substituteVars(value); 137 } 138 return value; 139 } 140 141 private static Pattern varPat = Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}"); 142 private static int MAX_SUBST = 20; 143 144 private String substituteVars(String expr) { 145 if (expr == null) { 146 return null; 147 } 148 Matcher match = varPat.matcher(""); 149 String eval = expr; 150 for (int s = 0; s < MAX_SUBST; s++) { 151 match.reset(eval); 152 if (!match.find()) { 153 return eval; 154 } 155 String var = match.group(); 156 var = var.substring(2, var.length() - 1); // remove ${ .. } 157 158 String val = getRaw(var); 159 if (val == null) { 160 val = System.getProperty(var); 161 } 162 163 if (val == null) { 164 return eval; // return literal ${var}: var is unbound 165 } 166 // substitute 167 eval = eval.substring(0, match.start()) + val + eval.substring(match.end()); 168 } 169 throw new IllegalStateException("Variable substitution depth too large: " + MAX_SUBST + " " + expr); 170 } 171 172 /** 173 * This is a stop gap fix for <link href="https://issues.apache.org/jira/browse/HADOOP-4416">HADOOP-4416</link>. 174 */ 175 public Class<?> getClassByName(String name) throws ClassNotFoundException { 176 return super.getClassByName(name.trim()); 177 } 178 179 /** 180 * Copy configuration key/value pairs from one configuration to another if a property exists in the target, it gets 181 * replaced. 182 * 183 * @param source source configuration. 184 * @param target target configuration. 185 */ 186 public static void copy(Configuration source, Configuration target) { 187 for (Map.Entry<String, String> entry : source) { 188 target.set(entry.getKey(), entry.getValue()); 189 } 190 } 191 192 /** 193 * Injects configuration key/value pairs from one configuration to another if the key does not exist in the target 194 * configuration. 195 * 196 * @param source source configuration. 197 * @param target target configuration. 198 */ 199 public static void injectDefaults(Configuration source, Configuration target) { 200 for (Map.Entry<String, String> entry : source) { 201 if (target.get(entry.getKey()) == null) { 202 target.set(entry.getKey(), entry.getValue()); 203 } 204 } 205 } 206 207 /** 208 * Returns a new XConfiguration with all values trimmed. 209 * 210 * @return a new XConfiguration with all values trimmed. 211 */ 212 public XConfiguration trim() { 213 XConfiguration trimmed = new XConfiguration(); 214 for (Map.Entry<String, String> entry : this) { 215 trimmed.set(entry.getKey(), entry.getValue().trim()); 216 } 217 return trimmed; 218 } 219 220 /** 221 * Returns a new XConfiguration instance with all inline values resolved. 222 * 223 * @return a new XConfiguration instance with all inline values resolved. 224 */ 225 public XConfiguration resolve() { 226 XConfiguration resolved = new XConfiguration(); 227 for (Map.Entry<String, String> entry : this) { 228 resolved.set(entry.getKey(), get(entry.getKey())); 229 } 230 return resolved; 231 } 232 233 // Canibalized from Hadoop <code>Configuration.loadResource()</code>. 234 private void parse(InputStream is) throws IOException { 235 try { 236 DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); 237 // ignore all comments inside the xml file 238 docBuilderFactory.setIgnoringComments(true); 239 DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); 240 Document doc = builder.parse(is); 241 parseDocument(doc); 242 } 243 catch (SAXException e) { 244 throw new IOException(e); 245 } 246 catch (ParserConfigurationException e) { 247 throw new IOException(e); 248 } 249 } 250 251 // Canibalized from Hadoop <code>Configuration.loadResource()</code>. 252 private void parse(Reader reader) throws IOException { 253 try { 254 DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); 255 // ignore all comments inside the xml file 256 docBuilderFactory.setIgnoringComments(true); 257 DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); 258 Document doc = builder.parse(new InputSource(reader)); 259 parseDocument(doc); 260 } 261 catch (SAXException e) { 262 throw new IOException(e); 263 } 264 catch (ParserConfigurationException e) { 265 throw new IOException(e); 266 } 267 } 268 269 // Canibalized from Hadoop <code>Configuration.loadResource()</code>. 270 private void parseDocument(Document doc) throws IOException { 271 try { 272 Element root = doc.getDocumentElement(); 273 if (!"configuration".equals(root.getTagName())) { 274 throw new IOException("bad conf file: top-level element not <configuration>"); 275 } 276 NodeList props = root.getChildNodes(); 277 for (int i = 0; i < props.getLength(); i++) { 278 Node propNode = props.item(i); 279 if (!(propNode instanceof Element)) { 280 continue; 281 } 282 Element prop = (Element) propNode; 283 if (!"property".equals(prop.getTagName())) { 284 throw new IOException("bad conf file: element not <property>"); 285 } 286 NodeList fields = prop.getChildNodes(); 287 String attr = null; 288 String value = null; 289 for (int j = 0; j < fields.getLength(); j++) { 290 Node fieldNode = fields.item(j); 291 if (!(fieldNode instanceof Element)) { 292 continue; 293 } 294 Element field = (Element) fieldNode; 295 if ("name".equals(field.getTagName()) && field.hasChildNodes()) { 296 attr = ((Text) field.getFirstChild()).getData().trim(); 297 } 298 if ("value".equals(field.getTagName()) && field.hasChildNodes()) { 299 value = ((Text) field.getFirstChild()).getData(); 300 } 301 } 302 303 if (attr != null && value != null) { 304 set(attr, value); 305 } 306 } 307 308 } 309 catch (DOMException e) { 310 throw new IOException(e); 311 } 312 } 313 314 /** 315 * Return a string with the configuration in XML format. 316 * 317 * @return a string with the configuration in XML format. 318 */ 319 public String toXmlString() { 320 return toXmlString(true); 321 } 322 323 public String toXmlString(boolean prolog) { 324 String xml; 325 try { 326 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 327 this.writeXml(baos); 328 baos.close(); 329 xml = new String(baos.toByteArray()); 330 } 331 catch (IOException ex) { 332 throw new RuntimeException("It should not happen, " + ex.getMessage(), ex); 333 } 334 if (!prolog) { 335 xml = xml.substring(xml.indexOf("<configuration>")); 336 } 337 return xml; 338 } 339 340 }