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