Coverage Report - org.kuali.rice.core.config.HierarchicalConfigParser
 
Classes in this File Line Coverage Branch Coverage Complexity
HierarchicalConfigParser
0%
0/130
0%
0/66
8.143
 
 1  
 /*
 2  
  * Copyright 2005-2007 The Kuali Foundation
 3  
  * 
 4  
  * 
 5  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  * 
 9  
  * http://www.opensource.org/licenses/ecl2.php
 10  
  * 
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.kuali.rice.core.config;
 18  
 
 19  
 import org.apache.log4j.Logger;
 20  
 import org.kuali.rice.core.util.XmlJotter;
 21  
 import org.w3c.dom.Document;
 22  
 import org.w3c.dom.Element;
 23  
 import org.w3c.dom.Node;
 24  
 import org.w3c.dom.NodeList;
 25  
 import org.xml.sax.SAXException;
 26  
 
 27  
 import javax.xml.parsers.DocumentBuilderFactory;
 28  
 import javax.xml.parsers.ParserConfigurationException;
 29  
 import javax.xml.transform.TransformerException;
 30  
 import java.io.FileInputStream;
 31  
 import java.io.FileNotFoundException;
 32  
 import java.io.IOException;
 33  
 import java.io.InputStream;
 34  
 import java.net.MalformedURLException;
 35  
 import java.net.URL;
 36  
 import java.util.*;
 37  
 
 38  
 /**
 39  
  * A configuration parser that can get properties already parsed passed in and
 40  
  * override them. Also, can do token replace based on values already parsed
 41  
  * using ${ } to denote tokens.
 42  
  * 
 43  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 44  
  */
 45  
 class HierarchicalConfigParser {
 46  0
     private static final Logger LOG = Logger.getLogger(HierarchicalConfigParser.class);
 47  
 
 48  
     private static final String VAR_START_TOKEN = "${";
 49  
 
 50  
     private static final String VAR_END_TOKEN = "}";
 51  
 
 52  
     public static final String ALTERNATE_BUILD_LOCATION_KEY = "alt.build.location";
 53  
 
 54  
 
 55  
     // this Map is for token replacement this represents the set of tokens that
 56  
     // has been made by a parent config file of the one this is parsing
 57  
     Map currentProperties;
 58  
 
 59  0
     HierarchicalConfigParser(final Map currentProperties) {
 60  0
         if (currentProperties == null) {
 61  0
             this.currentProperties = new LinkedHashMap();
 62  
         } else {
 63  0
             this.currentProperties = currentProperties;
 64  
         }
 65  0
     }
 66  
 
 67  
     public Map<String, Object> parse(String fileLoc) throws IOException {
 68  0
         Map<String, Object> fileProperties = new LinkedHashMap<String, Object>();
 69  0
         parse(fileLoc, fileProperties, true);
 70  0
         return fileProperties;
 71  
     }
 72  
 
 73  
     private void parse(String fileLoc, Map<String, Object> fileProperties, boolean baseFile) throws IOException {
 74  0
         InputStream configStream = getConfigAsStream(fileLoc);
 75  0
         if (configStream == null) {
 76  0
             LOG.warn("###############################");
 77  0
             LOG.warn("#");
 78  0
             LOG.warn("# Configuration file " + fileLoc + " not found!");
 79  0
             LOG.warn("#");
 80  0
             LOG.warn("###############################");
 81  0
             return;
 82  
         }
 83  
 
 84  0
         LOG.info("Parsing config: " + fileLoc);
 85  
 
 86  0
         if (!baseFile) {
 87  0
             fileProperties.put(fileLoc, new Properties());
 88  
         }
 89  0
         Properties props = (Properties) fileProperties.get(fileLoc);
 90  
         Document doc;
 91  
         try {
 92  0
             doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(configStream);
 93  0
             if (LOG.isDebugEnabled()) {
 94  0
                 LOG.debug("Contents of config " + fileLoc + ": \n" + XmlJotter.jotNode(doc, true));
 95  
             }
 96  0
         } catch (SAXException se) {
 97  0
             IOException ioe = new IOException("Error parsing config resource: " + fileLoc);
 98  0
             ioe.initCause(se);
 99  0
             throw ioe;
 100  0
         } catch (ParserConfigurationException pce) {
 101  0
             IOException ioe = new IOException("Unable to obtain document builder");
 102  0
             ioe.initCause(pce);
 103  0
             throw ioe;
 104  
         } finally {
 105  0
             configStream.close();
 106  0
         }
 107  
 
 108  0
         Element root = doc.getDocumentElement();
 109  
         // ignore the actual type of the document element for now
 110  
         // so that plugin descriptors can be parsed
 111  0
         NodeList list = root.getChildNodes();
 112  0
         StringBuffer content = new StringBuffer();
 113  0
         for (int i = 0; i < list.getLength(); i++) {
 114  0
             Node node = list.item(i);
 115  0
             if (node.getNodeType() != Node.ELEMENT_NODE)
 116  0
                 continue;
 117  0
             if (!"param".equals(node.getNodeName())) {
 118  0
                 LOG.warn("Encountered non-param config node: " + node.getNodeName());
 119  0
                 continue;
 120  
             }
 121  0
             Element param = (Element) node;
 122  0
             String name = param.getAttribute("name");
 123  0
             Boolean override = Boolean.TRUE;
 124  0
             if (param.getAttribute("override") != null && param.getAttribute("override").trim().length() > 0) {
 125  0
                 override = new Boolean(param.getAttribute("override"));
 126  
             }
 127  0
             if (name == null) {
 128  0
                 LOG.error("Unnamed parameter in config resource '" + fileLoc + "': " + XmlJotter.jotNode(param));
 129  0
                 continue;
 130  
             }
 131  0
             NodeList contents = param.getChildNodes();
 132  
             // accumulate all content (preserving any XML content)
 133  
             try {
 134  0
                 content.setLength(0);
 135  0
                 for (int j = 0; j < contents.getLength(); j++) {
 136  0
                     content.append(XmlJotter.writeNode(contents.item(j), true));
 137  
                 }
 138  
                 String contentValue;
 139  
                 try {
 140  0
                     contentValue = resolvePropertyTokens(content.toString(), fileProperties);
 141  0
                 } catch (Exception e) {
 142  0
                     LOG.error("Exception caught parsing " + content, e);
 143  0
                     throw new RuntimeException(e);
 144  0
                 }
 145  0
                 if (name.equals("config.location")) {
 146  0
                     if (!contentValue.contains(ALTERNATE_BUILD_LOCATION_KEY)) {
 147  0
                         parse(contentValue, fileProperties, false);
 148  
                     }
 149  
                 } else {
 150  0
                     if (props == null) {
 151  0
                         updateProperties(fileProperties, override, name, contentValue, fileProperties);
 152  
                     } else {
 153  0
                         updateProperties(props, override, name, contentValue, fileProperties);
 154  
                     }
 155  
                 }
 156  0
             } catch (TransformerException te) {
 157  0
                 IOException ioe = new IOException("Error obtaining parameter '" + name + "' from config resource: " + fileLoc);
 158  0
                 ioe.initCause(te);
 159  0
                 throw ioe;
 160  0
             }
 161  
         }
 162  0
         LOG.info("Parsed config: " + fileLoc);
 163  0
     }
 164  
 
 165  
     private void updateProperties(Map props, Boolean override, String name, String value, Map<String, Object> fileProperties) {
 166  0
         if (value == null || "null".equals(value)) {
 167  0
             LOG.warn("Not adding property [" + name + "] because it is null - most likely no token could be found for substituion.");
 168  0
             return;
 169  
         }
 170  0
         if (override) {
 171  0
             setProperty(props, name, value);
 172  
         } else {
 173  0
             if (!override && !fileProperties.containsKey(name)) {
 174  0
                 setProperty(props, name, value);
 175  
             }
 176  
         }
 177  0
     }
 178  
 
 179  
     private void setProperty(Map map, String name, String value) {
 180  0
         if (map.containsKey(name)) {
 181  0
             map.remove(name);
 182  
         }
 183  0
         map.put(name, value);
 184  0
     }
 185  
 
 186  
     public static InputStream getConfigAsStream(String fileLoc) throws MalformedURLException, IOException {
 187  0
         if (fileLoc.lastIndexOf("classpath:") > -1) {
 188  0
             String configName = fileLoc.split("classpath:")[1];
 189  
             /*ClassPathResource cpr = new  ClassPathResource(configName, Thread.currentThread().getContextClassLoader());
 190  
                         if (cpr.exists()) {
 191  
                             return cpr.getInputStream();
 192  
                         } else {
 193  
                             return null;
 194  
                         }*/
 195  0
             return Thread.currentThread().getContextClassLoader().getResourceAsStream(configName);
 196  0
         } else if (fileLoc.lastIndexOf("http://") > -1 || fileLoc.lastIndexOf("file:/") > -1) {
 197  0
             return new URL(fileLoc).openStream();
 198  
         } else {
 199  
             try {
 200  0
                 return new FileInputStream(fileLoc);
 201  0
             } catch (FileNotFoundException e) {
 202  0
                 return null; // logged by caller
 203  
             }
 204  
         }
 205  
     }
 206  
 
 207  
     private String resolvePropertyTokens(String content, Map<String, Object> properties) {
 208  
         // TODO: consider implementing with iteration instead of recursion, and using the jakarta commons support for
 209  
         // parameter expansion
 210  0
         if (content.contains(VAR_START_TOKEN)) {
 211  0
             int tokenStart = content.indexOf(VAR_START_TOKEN);
 212  0
             int tokenEnd = content.indexOf(VAR_END_TOKEN, tokenStart + VAR_START_TOKEN.length());
 213  0
             if (tokenEnd == -1) {
 214  0
                 throw new RuntimeException("No ending bracket on token in value " + content);
 215  
             }
 216  0
             String token = content.substring(tokenStart + VAR_START_TOKEN.length(), tokenEnd);
 217  0
             String tokenValue = null;
 218  
 
 219  
             // get all the properties from all the potentially nested configs in
 220  
             // the master set
 221  
             // of propertiesUsed. Do it now so that all the values are available
 222  
             // for token replacement
 223  
             // next iteration
 224  
             //
 225  
             // The properties map is sorted with the top of the hierarchy as the
 226  
             // first element in the iteration, however
 227  
             // we want to include starting with the bottom of the hierarchy, so
 228  
             // we will iterate over the Map in reverse
 229  
             // order (this reverse iteration fixes the bug referenced by EN-68.
 230  0
             LinkedList<Map.Entry<String, Object>> propertiesList = new LinkedList<Map.Entry<String, Object>>(properties.entrySet());
 231  0
             Collections.reverse(propertiesList);
 232  0
             for (Map.Entry<String, Object> config : propertiesList) {
 233  0
                 if (!(config.getValue() instanceof Properties)) {
 234  0
                     if (token.equals(config.getKey())) {
 235  0
                         tokenValue = (String) config.getValue();
 236  0
                         break;
 237  
                     } else {
 238  
                         continue;
 239  
                     }
 240  
                 }
 241  0
                 Properties configProps = (Properties) config.getValue();
 242  0
                 tokenValue = (String) configProps.get(token);
 243  0
                 if (tokenValue != null) {
 244  0
                     break;
 245  
                 }
 246  
 
 247  0
                 LOG.debug("Found token " + token + " in included config file " + config.getKey());
 248  0
             }
 249  
 
 250  0
             if (tokenValue == null) {
 251  0
                 if (token.contains(ALTERNATE_BUILD_LOCATION_KEY)) {
 252  0
                     return token;
 253  
                 }
 254  0
                 LOG.debug("Did not find token " + token + " in local properties.  Looking in parent.");
 255  0
                 tokenValue = (String) this.currentProperties.get(token);
 256  0
                 if (tokenValue == null) {
 257  0
                     LOG.debug("Did not find token " + token + " in parent properties.  Looking in system properties.");
 258  0
                     tokenValue = System.getProperty(token);
 259  0
                     if (tokenValue == null) {
 260  0
                         LOG.warn("Did not find token " + token + " in all available configuration properties!");
 261  
                     } else {
 262  0
                         LOG.debug("Found token " + token + " in sytem properties");
 263  
                     }
 264  
                 } else {
 265  0
                     LOG.debug("Found token " + token + "=" + tokenValue + " in parent.");
 266  
                 }
 267  
             } else {
 268  0
                 LOG.debug("Found token in local properties");
 269  
             }
 270  
 
 271  
             // NOTE: this will result in any variables that are not found being replaced with the string 'null'
 272  
             // this is the place to change that behavior (e.g. if (tokenvalue == null) do something else)
 273  0
             String tokenizedContent = content.substring(0, tokenStart) + tokenValue + content.substring(tokenEnd + VAR_END_TOKEN.length(), content.length());
 274  
             // give it back to this method so we can have multiple tokens per
 275  
             // config entry.
 276  0
             return resolvePropertyTokens(tokenizedContent, properties);
 277  
         }
 278  
 
 279  0
         return content;
 280  
     }
 281  
 }