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