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