Package jeeves.server.overrides

Source Code of jeeves.server.overrides.ConfigurationOverrides

package jeeves.server.overrides;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.spi.LoggerRepository;
import org.fao.geonet.Constants;
import org.fao.geonet.utils.Log;
import org.fao.geonet.utils.XPath;
import org.fao.geonet.utils.Xml;
import org.jdom.*;
import org.jdom.filter.Filter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;

import javax.servlet.ServletContext;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* This class assists JeevesEngine by allowing certain configurations to be overridden.
* <p/>
* <p/>
* The idea is to allow configurations to have seconds overridden for a specific server deployment.
* A common scenario is to have test and production instances with different configurations.  In both configurations
* 90% of the configuration is the same but certain parts need to be updated.
* <p/>
* This class allows an override file to be specified as a system property or a servlet init parameter:
* <em>jeeves.configuration.overrides.file</em>.
* <p/>
* The order of resolution is:
* <ol>
* <li>System property with key:  <em>{servlet.getServletContext().getServletContextName()}.jeeves.configuration.overrides.file</em></li>
* <li>Servlet init parameter with key:  <em>jeeves.configuration.overrides.file</em></li>
* <li>System property with key:  <em>jeeves.configuration.overrides.file</em></li>
* <li>Servlet <em>context</em> init parameters with key:  <em>jeeves.configuration.overrides.file</em></li>
* </ol>
*
* The configuration in the override file will override settings in the "standard" loaded configuration
* files.
* <p/>
* The overrides file can be a file (relative to the servlet base) or a URL
* <p/>
* Note:  When writing the xpath the root not should not be in the xpath and the path should not
* start with a /.
* <p/>
* The override configuration structure is an XML file as follows:
* <pre><[[CDATA[
* <overrides>
    <!-- import values.  The imported values are put at top of sections -->
    <import file="./imported-config-overrides.xml" />
     <!-- properties allow some properties to be defined that will be substituted -->
     <!-- into text or attributes where ${property} is the substitution pattern -->
     <!-- The properties can reference other properties -->
     <properties>
         <enabled>true</enabled>
         <dir>xml</dir>
         <aparam>overridden</aparam>
     </properties>
     <!-- A regular expression for matching the file affected. -->
     <file name=".*WEB-INF/config\.xml">
         <!-- This example will update the file attribute of the xml element with the name attribute 'countries' -->
         <replaceAtt xpath="default/gui/xml[@name = 'countries']" attName="file" value="${dir}/europeanCountries.xml"/>
         <!-- if there is no value then the attribute is removed -->
         <replaceAtt xpath="default/gui" attName="removeAtt"/>
         <!-- If the attribute does not exist it is added -->
         <replaceAtt xpath="default/gui" attName="newAtt" value="newValue"/>

         <!-- This example will replace all the xml in resources with the contained xml -->
         TODO

         <!-- This example simple replaces the text of an element -->
         <replaceText xpath="default/language">${lang}</replaceText>
         <!-- This examples shows how only the text is replaced not the nodes -->
         <replaceText xpath="default/gui">ExtraText</replaceText>
         <!-- append xml as a child to a section (If xpath == "" then that indicates the root of the document),
              this case adds nodes to the root document -->
         <addXML xpath=""><newNode/></addXML>
         <!-- append xml as a child to a section, this case adds nodes to the root document -->
         <addXML xpath="default/gui"><newNode2/></addXML>
         <!-- remove a single node -->
         <removeXML xpath="default/gui/xml[@name = countries2]"/>
     </file>
     <file name=".*WEB-INF/config2\.xml">
         <replaceText xpath="default/language">de</replaceText>
     </file>
     <!-- a normal file tag is for updating XML configuration files -->
     <!-- textFile tags are for updating normal text files like sql files -->
     <textFile name="test-sql.sql">
       <!-- each line in the text file is matched against the linePattern attribute and the new value is used for substitution -->
       <update linePattern="(.*) Relations">$1 NewRelations</update>
       <update linePattern="(.*)relatedId(.*)">$1${aparam}$2</update>
     </textFile>
     <!-- configure the spring aspects of geonetwork -->
     <spring>
         <!-- import a complete spring xml file -->
         <import file="./config-spring-overrides.xml"/>
         <!-- declare a file as a spring properties override file: See http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/beans/factory/config/PropertyOverrideConfigurer.html -->
         <propertyOverrides file="./config-property-overrides.properties" />
         <!-- set a property on one bean to reference another bean -->
         <set bean="beanName" property="propertyName" ref="otherBeanName"/>
         <!-- add a references to a bean to a property on another bean.  This assumes the property is a collection -->
         <add bean="beanName" property="propertyName" ref="otherBeanName"/>
      </spring>
</overrides>
* ]]></pre>
*
*    A original proposal about the overrides are at:
*   <a href="http://trac.osgeo.org/geonetwork/wiki/ConfigOverride">http://trac.osgeo.org/geonetwork/wiki/ConfigOverride</a>
*   The API has changed slightly since it was written but the principals remain the same

*/
public class ConfigurationOverrides {


    private static final String CONFIG_OVERRIDES_FILENAME = "config-overrides.xml";
    private SpringPropertyOverrides springPropertyOverrides;

    enum Updates {
        REPLACEATT,
        REPLACEXML,
        ADDXML,
        REMOVEXML,
        REPLACETEXT;
    }

    static final String LOGFILE_XPATH = "logging/logFile";
    public static final String OVERRIDES_KEY = "jeeves.configuration.overrides.file";
    private static final String ATTNAME_ATTR_NAME = "attName";
    private static final String VALUE_ATTR_NAME = "value";
    private static final String XPATH_ATTR_NAME = "xpath";
    private static final String FILE_NODE_NAME = "file";
    private static final String TEXT_FILE_NODE_NAME = "textFile";
    private static final String FILE_NAME_ATT_NAME = "name";
    private static final Pattern PROP_PATTERN = Pattern.compile("\\$\\{(.+?)\\}");
    @SuppressWarnings("serial")
    private static final Filter ELEMENTS_FILTER = new Filter() {
        public boolean matches(Object obj) {
            return obj instanceof Element;
        }
    };
    @SuppressWarnings("serial")
    private static final Filter TEXTS_FILTER = new Filter() {
        public boolean matches(Object obj) {
            return obj instanceof Text;
        }
    };
    private static final List<Namespace> WEB_XML_NS = new ArrayList<Namespace>();
    static {
        WEB_XML_NS.add(Namespace.getNamespace("http://java.sun.com/xml/ns/j2ee"));
    }
   
    public static final ConfigurationOverrides DEFAULT = new ConfigurationOverrides(null);
    private String _overrides;

    public ConfigurationOverrides(final String overrides) {
        this._overrides = overrides;
    }

    /**
     * Update the logging configuration so that it uses the configuration defined in the overrides rather than the defaults
     *
     * @param context the servlet context that is loaded (maybe null.  If null appPath is used to resolve configuration files like: /WEB-INF/configuration-overrides.xml
     * @param appPath The path to the webapplication root.  If servlet is null (and therefore getResource cannot be used, this path is used to file files)
     */
    public void updateLoggingAsAccordingToOverrides(ServletContext context, String appPath) throws JDOMException, IOException {
        String resource = lookupOverrideParameter(context, appPath);

        ServletResourceLoader loader = new ServletResourceLoader(context,appPath);
        Element xml = loader.loadXmlResource(resource);
        if (xml != null) {
            doUpdateLogging(xml, loader);
        }
    }

    /**
     * This method is default visibility for testing
     */
    void doUpdateLogging(Element overrides, ResourceLoader loader) throws JDOMException, IOException {
        List<?> logOverides = Xml.selectNodes(overrides, LOGFILE_XPATH);
        Properties p = new Properties();
        for (Object logOveride : logOverides) {
            String value = ((Content) logOveride).getValue();
            InputStream in = loader.loadInputStream(value);
            if (in != null) {
                try {
                    p.load(in);
                } finally {
                    in.close();
                }
            } else {
                throw new IllegalArgumentException("log configuration file " + value + " was not found");
            }
        }
        if (logOverides.size() > 0) {
            LoggerRepository loggerRepo = Logger.getRootLogger().getLoggerRepository();
            loggerRepo.resetConfiguration();
            PropertyConfigurator configurator = new PropertyConfigurator();
            configurator.doConfigure(p, loggerRepo);
        }
    }

    /**
     *
     *
     * @param configFile the path to the configuration file that has been loaded (and is the configRoot).  This is used to identify overrides
     * @param context
     *@param appPath The path to the webapplication root.  If servlet is null (and therefore getResource cannot be used, this path is used to file files)
     * @param configElement the root element of the configuration (obtained by loading configFile)    @throws JDOMException
     * @throws IOException
     */
    public void updateWithOverrides(String configFile, ServletContext context, String appPath, Element configElement) throws JDOMException, IOException {
        String resource = lookupOverrideParameter(context,appPath);

        ServletResourceLoader loader = new ServletResourceLoader(context,appPath);
        updateConfig(loader,resource,configFile, configElement);
    }

    private void updateConfig(ResourceLoader loader, String overridesResource, String configFilePath, Element configRoot) throws JDOMException, IOException {
        Element overrides = loader.loadXmlResource(overridesResource);
        if (overrides == null) {
            return;
        }
        Properties properties = loadProperties(overrides);
        @SuppressWarnings("unchecked")
        List<Element> files = overrides.getChildren(FILE_NODE_NAME);
        for (Element file : files) {
            String expectedfileName = file.getAttributeValue(FILE_NAME_ATT_NAME);

            if (Pattern.matches(expectedfileName, configFilePath.replace(File.separator, "/"))) {
              Log.info(Log.JEEVES, "Overrides being applied to configuration file: " + expectedfileName);

                @SuppressWarnings("unchecked")
                List<Element> elements = file.getChildren();
                for (Element element : elements) {
                    switch (Updates.valueOf(element.getName().toUpperCase())) {
                        case ADDXML:
                            addXml(properties, element, configRoot);
                            break;
                        case REMOVEXML:
                            removeXml(properties, element, configRoot);
                            break;
                        case REPLACEXML:
                            replaceXml(properties, element, configRoot);
                            break;
                        case REPLACEATT:
                            replaceAtts(properties, element, configRoot);
                            break;
                        case REPLACETEXT:
                            replaceText(properties, element, configRoot);
                            break;
                        default:
                            throw new IllegalArgumentException(element.getName() + " is not a recognized update tag");
                    }
                }
            }
        }
    }

    private void removeXml(Properties properties, Element elem, Element configRoot) throws JDOMException {
        String xpath = getXPath(elem);
        List<Content> matches = xpathLookup(configRoot, xpath);
        info("Removing xml elements: " + xpath);

        for (Content match : matches) {
            match.detach();
        }
    }

    private void addXml(Properties properties, Element elem, Element configRoot) throws JDOMException {

        List<Content> newXml = updateProperties(properties, elem);
        String xpath = getXPath(elem);
        info("Adding xml elements to " + xpath);
        debug("Elements added are:" + Xml.getString(new Element("toAdd").addContent(newXml)));
        for (Content content : newXml) {
            content.detach();
        }

        if (xpath.trim().equals("")) {
            debug("Adding to root element");
            configRoot.addContent(newXml);
        } else {
            List<Content> matches = xpathLookup(configRoot, xpath);
            for (Content match : matches) {
                if (match instanceof Element) {
                    Element element = (Element) match;
                    debug("Adding xml to " + XPath.getXPath(element));
                    element.addContent(newXml);
                } else {
                    throw new IllegalArgumentException("the xpath of an Add XML overrides must select elements only");
                }
            }
        }
    }

    private void replaceXml(Properties properties, Element elem, Element configRoot) throws JDOMException {
        String xpath = getXPath(elem);
        List<Content> matches = xpathLookup(configRoot, xpath);
        List<Content> newXml = updateProperties(properties, elem);
        info("Replacing child xml elements of " + xpath);
        debug("New elements are:" + Xml.getString(new Element("toAdd").addContent(newXml)));
        for (Content content : newXml) {
            content.detach();
        }
        for (Content toUpdate : matches) {
            if (toUpdate instanceof Element) {
                Element element = (Element) toUpdate;
                debug("replacingXML of " + XPath.getXPath(element));
                element.setContent(newXml);
            } else {
                throw new IllegalArgumentException("the xpath of an Replace XML override must select elements only");
            }

        }
    }

    private void replaceText(Properties properties, Element elem, Element configRoot) throws JDOMException {
        String xpath = getXPath(elem);
        String text = updatePropertiesInText(properties, elem.getText());
        info("Replacing text of " + xpath);
        debug("New text is:" + text);

        List<Content> matches = xpathLookup(configRoot, xpath);
        for (Content toUpdate : matches) {
            if (toUpdate instanceof Element) {
                Element element = (Element) toUpdate;
                debug("replacing Text of " + XPath.getXPath(element));
                @SuppressWarnings("unchecked")
                List<Text> textContent = toList(element.getDescendants(TEXTS_FILTER));

                if (textContent.size() > 0) {
                    for (int i = 0; i < textContent.size(); i++) {
                        Text text1 = textContent.get(i);
                        if (i == 0 && text.length() > 0) {
                            text1.setText(text);
                        } else {
                            text1.detach();
                        }
                    }
                } else {
                    element.addContent(text);
                }
            } else {
                throw new IllegalArgumentException("the xpath of an Replace Text override must select elements only");
            }

        }
    }

    private void replaceAtts(Properties properties, Element elem, Element configRoot) throws JDOMException {
        String xpath = getXPath(elem);
        List<Content> matches = xpathLookup(configRoot, xpath);
        String attName = getCaseInsensitiveAttValue(elem, ATTNAME_ATTR_NAME, true);
        String newValue = updatePropertiesInText(properties, getCaseInsensitiveAttValue(elem, VALUE_ATTR_NAME, false));

        info("Replacing attribute " + attName + " of node " + xpath);
        debug("New attribute is:" + newValue);


        for (Content toUpdate : matches) {
            if (toUpdate instanceof Element) {
                Element element = (Element) toUpdate;
                debug("Updating attibute of node " + XPath.getXPath(element));
                if (newValue == null) {
                    element.removeAttribute(attName);
                } else {
                    element.setAttribute(attName, newValue);
                }
            } else {
                throw new IllegalArgumentException("the xpath of an Replace Attribute override must select elements only");
            }
        }
    }

    private List<Content> xpathLookup(Element configRoot, String xpath) throws JDOMException {
        // Register all namespaces which may be required to solve xpath
        ArrayList<Namespace> namespaces = new ArrayList<Namespace>();
        for (Iterator<?> iterator = configRoot.getAdditionalNamespaces().iterator(); iterator.hasNext();) {
            Namespace ns = (Namespace) iterator.next();
            namespaces.add(ns);
        }
       
        List<?> objects = Xml.selectNodes(configRoot, xpath, namespaces);
        List<Content> elements = new ArrayList<Content>();
       
        for (Object object : objects) {
            if (object instanceof Content) {
                elements.add((Element) object);
            } else {
                throw new Error("How can this not be a content element");
            }
        }
        return elements;
    }

    private Properties loadProperties(Element overrides) {
        Properties properties = new Properties();
        @SuppressWarnings("unchecked")
        List<Element> pElem = overrides.getChildren("properties");
        for (Element element : pElem) {
            @SuppressWarnings("unchecked")
            List<Element> props = element.getChildren();
            for (Element prop : props) {
                String key = prop.getName();
                String value = prop.getTextTrim();
                properties.put(key, value);
            }
        }

        while (!resolve(properties)) ;
        return properties;
    }

    private String getXPath(Element elem) {
        return getCaseInsensitiveAttValue(elem, XPATH_ATTR_NAME, true);
    }

    private String getCaseInsensitiveAttValue(Element elem, String name, boolean exceptionOnFailure) {
        @SuppressWarnings("unchecked")
        List<Attribute> atts = elem.getAttributes();
        for (Attribute att : atts) {
            if (att.getName().equalsIgnoreCase(name)) {
                return att.getValue();
            }
        }
        if (exceptionOnFailure)
            throw new AssertionError(elem.getName() + " does not have a '" + name + "' attribute");
        else
            return null;
    }

    private List<Content> updateProperties(Properties properties, Element elem) {
        Element clone = (Element) elem.clone();
        @SuppressWarnings("unchecked")
        Iterator<Element> iter = clone.getDescendants(ELEMENTS_FILTER);

        List<Element> elems = toList(iter);

        for (Element next : elems) {
            @SuppressWarnings("unchecked")
            List<Attribute> atts = next.getAttributes();
            for (Attribute att : atts) {
                if (!att.getName().equalsIgnoreCase(XPATH_ATTR_NAME)) {
                    String updatedValue = updatePropertiesInText(properties, att.getValue());
                    att.setValue(updatedValue);
                }
            }
        }
        @SuppressWarnings("unchecked")
        Iterator<Text> iter2 = clone.getDescendants(TEXTS_FILTER);

        List<Text> textNodes = toList(iter2);

        for (Text text : textNodes) {
            String updatedText = updatePropertiesInText(properties, text.getText());
            text.setText(updatedText);
        }

        @SuppressWarnings("unchecked")
        List<Content> newXml = new ArrayList<Content>(clone.getChildren());
        for (Content content : newXml) {
            content.detach();
        }

        return newXml;
    }

    private<E> List<E> toList(Iterator<E> iter) {
        ArrayList<E> elems = new ArrayList<E>();
        while (iter.hasNext()) {
            elems.add(iter.next());
        }
        return elems;
    }

    String updatePropertiesInText(Properties properties, String value) {
        if (value == null) {
            return null;
        }
        String updatedValue = value;
        Matcher matcher = PROP_PATTERN.matcher(updatedValue);
        while (matcher.find()) {
            String propKey = matcher.group(1);
            String propValue = properties.getProperty(propKey);

            if (propValue == null) {
                throw new IllegalArgumentException("Found a reference to a variable: " + propKey + " which is not a valid property.  Check the spelling");
            }
            String dataToReplace = matcher.group(0);
            updatedValue = updatedValue.replace(dataToReplace, propValue);
        }
        return updatedValue;
    }

    private boolean resolve(Properties properties) {
        boolean finishedResolving = true;
        Set<Map.Entry<Object, Object>> entries = properties.entrySet();
        for (Map.Entry<Object, Object> entry : entries) {
            String key = entry.getKey().toString();
            String value = entry.getValue().toString();
            Matcher matcher = PROP_PATTERN.matcher(value);

            if (matcher.find())
                finishedResolving = false;

            String updatedValue = updatePropertiesInText(properties, value);
            properties.put(key, updatedValue);
        }
        return finishedResolving;
    }

    private void info(String msg) {
        Log.info(Log.JEEVES, msg);
    }

    private void debug(String msg) {
        Log.debug(Log.JEEVES, msg);
    }

    private String lookupOverrideParameter(ServletContext context,String appPath) throws JDOMException, IOException {
        if (_overrides != null) {
            return _overrides;
        }
        String resource;
        if(context == null) {
            resource = lookupOverrideParamFromAppPath(appPath);
        } else {
            resource = lookupOverrideParamFromServlet(context);
        }
        return resource;
    }

    private String lookupOverrideParamFromAppPath(String appPath) throws JDOMException, IOException {
        File webInf = new File(appPath,"WEB-INF");
    File webxmlFile = new File(webInf,"web.xml");
        String resource = null;
        if(webxmlFile.exists()) {
            Element webXML = Xml.loadFile(webxmlFile);
            Namespace namespace = webXML.getNamespace();
            String webappName = webXML.getChildTextTrim("display-name", namespace);

            if(webappName == null || webappName.isEmpty()) {
                webappName = "geonetwork";
            }
            resource = System.getProperty(webappName+"."+OVERRIDES_KEY);
           
        }
        if (resource == null || resource.trim().isEmpty()) {
          resource = lookupOverrideParamFromConfigFile(new File(webInf,CONFIG_OVERRIDES_FILENAME).toURI().toURL());
        }
        if (resource == null || resource.trim().isEmpty()) {
            resource = System.getProperty(OVERRIDES_KEY);
        }
        return resource;
    }

    @SuppressWarnings("unchecked")
  private String lookupOverrideParamFromConfigFile(URL url) throws IOException, JDOMException {
      try {
      Element config = Xml.loadFile(url);
      StringBuilder builder = new StringBuilder();
      for(Element elem : (List<Element>)config.getChildren("override")) {
        if(builder.length() > 0) {
          builder.append(',');
        }
        builder.append(elem.getTextTrim());
      }
      return builder.toString();
      } catch (FileNotFoundException e) {
        return null;
      } catch (NullPointerException e) { //If the url is null
    return null;
  }
  }

  private String lookupOverrideParamFromServlet(ServletContext context) throws IOException, JDOMException {
        String resource;
        resource = System.getProperty(context.getServletContextName()+"."+OVERRIDES_KEY);
        if (resource == null || resource.trim().isEmpty()) {
            resource = System.getProperty(OVERRIDES_KEY);
        }
        if (resource == null || resource.trim().isEmpty()) {
            resource = lookupOverrideParamFromConfigFile(context.getResource("/WEB-INF/" + CONFIG_OVERRIDES_FILENAME));
        }
        if (resource == null || resource.trim().isEmpty()) {
            resource = context.getInitParameter(OVERRIDES_KEY);
        }
        return resource;
    }

    public static abstract class ResourceLoader {
        protected InputStream loadInputStream(String resource) throws IOException {
            File file = resolveFile(resource);
            if(file == null) {
                return fallbackInputStream(resource);
            } else {
                return new FileInputStream(file);
            }
        }
        protected abstract File resolveFile(String resource) throws IOException;
        protected abstract InputStream fallbackInputStream(String resource) throws IOException;
        protected String resolveImportFileName(String importResource, String baseResource) {
            String resolved = resolveRelative(importResource, baseResource, File.separator);
            if(resolved.equals(importResource)) {
                return resolveRelative(importResource, baseResource, "/");
            } else {
                return resolved;
            }
        }

        protected String resolveRelative(String importResource, String baseResource, String sep) {
            String baseDir = baseResource;
            int lastSep = baseDir.lastIndexOf(sep);
            if(lastSep > -1) {
                baseDir = baseDir.substring(0,lastSep);
            }
            String back = ".." + sep;
            if(importResource.startsWith(back)) {
                return baseDir+sep+".."+sep+importResource;
            } else if(importResource.startsWith(back.substring(1))){
                return baseDir+sep+importResource;
            } else {
                return importResource;
            }
        }

        public final String loadStringResource(String resource) throws JDOMException, IOException {
            InputStream in = loadInputStream(resource);
            BufferedReader reader = null;
            if (in != null) {
                try {
                    reader = new BufferedReader(new InputStreamReader(in, Charset.forName(Constants.ENCODING)));
                    StringBuilder data = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        data.append(line);
                    }
                    return data.toString();
                } finally {
                    IOUtils.closeQuietly(in);
                    if(reader != null) {
                        IOUtils.closeQuietly(reader);
                    }
                }
            } else {
                return null;
            }
        }

        public final Element loadXmlResource(String resource) throws IOException {
          Element loadedResource = null;
          if(resource == null) {
              return null;
          }
          String[] resources = resource.split(",");
            for (String string : resources) {
                if (!string.trim().isEmpty()) {
                    InputStream in = loadInputStream(string);
                    if (in != null) {
                        try {
                            try {
                                Element element = Xml.loadStream(in);
                                Element loaded = resolveImports(element, string);
                                if (loadedResource == null) {
                                    loadedResource = loaded;
                                } else {
                                    mergeElements(loadedResource, loaded);
                                }
                            } catch (JDOMException e) {
                                throw new IOException(e);
                            }
                        } finally {
                            in.close();
                        }
                    } else {
                        if(Log.isDebugEnabled(Log.JEEVES))
                            Log.debug(Log.JEEVES, "Unable to load Configuration Override resource: " + string);
                    }
                }
            }
            return loadedResource;
        }

        private Element resolveImports(Element baseElement, String baseResource) throws JDOMException, IOException {
            @SuppressWarnings("unchecked")
            List<Element> imports = new ArrayList<Element>(baseElement.getChildren("import"));

            for (Element anImport : imports) {
                anImport.detach();
                String file = anImport.getAttributeValue("file");

                Element importedXml = loadXmlResource(resolveImportFileName(file, baseResource));
                mergeElements(baseElement, importedXml);
               
            }
            return baseElement;
        }
    private void mergeElements(Element baseElement, Element importedXml) throws JDOMException {
            @SuppressWarnings("unchecked")
            List<Element> children = new ArrayList<Element>(importedXml.getChildren());
            for (Element toMerge : children) {
                toMerge.detach();
                if (toMerge.getName().equalsIgnoreCase("file")) {
                    String xpath = "file[@name = '" + toMerge.getAttributeValue("name") + "']";
                    merge(baseElement, toMerge, xpath, false);
                } else if (toMerge.getName().equalsIgnoreCase("properties")) {
                    merge(baseElement, toMerge, toMerge.getName(), true);
                } else if (toMerge.getName().equalsIgnoreCase("textFile")) {
                    String xpath = "textFile[@name = '" + toMerge.getAttributeValue("name") + "']";
                    merge(baseElement, toMerge, xpath, false);
                } else {
                    merge(baseElement, toMerge, toMerge.getName(), false);
                }
            }
        }

        private void merge(Element baseElement, Element toMerge, String xpath, boolean overrideImports ) throws JDOMException {
            Element mergeTarget = Xml.selectElement(baseElement, xpath);
            if(mergeTarget != null) {
                @SuppressWarnings("unchecked")
                Collection<Content> contentToAdd = detach(toMerge.getContent());
                if(overrideImports) {
                    contentToAdd = filterOutExistingElements(mergeTarget, contentToAdd);
                }
                mergeTarget.addContent(0,contentToAdd);
            } else {
                baseElement.addContent(toMerge);
            }
        }

        private Collection<Content> filterOutExistingElements(Element mergeTarget, Collection<Content> contentToAdd) {
            ArrayList<Content> toAdd = new ArrayList<Content>();
            for (Content c : contentToAdd)
            {
                if(c instanceof Element) {
                    Element e = (Element) c.detach();
                    if(mergeTarget.getChild(e.getName()) == null) {
                        toAdd.add(e);
                    }
                } else {
                    toAdd.add(c);
                }
            }
            contentToAdd = toAdd;
            return contentToAdd;
        }

        private Collection<Content> detach(List<Content> content) {
            ArrayList<Content> al = new ArrayList<Content>(content);
            for (Content o : al) {
                o.detach();
            }
            return al;
        }
    }

    static class ServletResourceLoader extends ResourceLoader {
        private final ServletContext context;
        private String appPath;

        ServletResourceLoader(ServletContext context, String appPath) {
            this.context = context;
            this.appPath = appPath;
        }

        @Override
        protected File resolveFile(String resource) throws IOException {
            File file = null;
            File testPath = new File(resource);

            if(testPath.exists()) {
                file = testPath;
            }
            if(file ==null && context!=null) {
                String path = context.getRealPath(resource);
                if(path != null) {
                    testPath = new File(path);
                    if(testPath.exists()) {
                        file = testPath;
                    }
                }
            }
            if(file == null && appPath != null) {
                File testFile = new File(appPath, resource);
                if (testFile.exists()) {
                    file = testFile;
                }
            }

            if(file == null) {
                URL url = Thread.currentThread().getContextClassLoader().getResource(resource);
                if (url != null) {
                    File testFile = new File(url.getFile());
                    if (testFile.exists()) {
                        file = testFile;
                    } else {
                        testFile = new File(url.getPath());
                        if (testFile.exists()) {
                            file = testFile;
                        }
                    }
                }
            }
            return file;
        }

        @Override
        protected InputStream fallbackInputStream(String resource) throws IOException {

            if (resource != null) {
                InputStream in;
                try {
                    // try resource as a url
                    in = new URL(resource).openStream();
                } catch (MalformedURLException e) {
                    URL url;
                    try {
                        if(context != null) {
                            // try to get resource from the servlet context if servlet is non-null
                            url = context.getResource(resource);
                        } else {
                            // fall back to appPath is servlet is null
                            File appBasedFile = new File(appPath,resource);
                            if(appBasedFile.exists()) {
                                url = appBasedFile.toURI().toURL();
                            } else {
                                url = null;
                            }
                       }
                    } catch (MalformedURLException e2) {
                        url = null;
                    }
                    if (url == null) {
                        File file = new File(resource);
                        if (file.exists()) {
                            in = new FileInputStream(file);
                        } else {
                          file = new File(resource.replace('/', '\\'));
                          if(file.exists()) {
                            in = new FileInputStream(file);
                          } else {
                            throw new IllegalArgumentException("The resource file " + resource + " is not a file and not a web resource.  Perhaps a leading / was forgotten?");
                          }
                        }
                    } else {
                        in = url.openStream();
                    }
                }
                return in;
            }
            return null;
        }
    }

    /**
     * Load an XML file and applies the overrides to the file
     *
     *
     * @param servletRelativePath file to load.  It is assumed to be with the servlet and is assumed to start with /.
     * @param context the ServletContext to use for locating the file
     * @return
     */
    public Element loadXmlFileAndUpdate(String servletRelativePath, ServletContext context) {
        String appPath = context.getContextPath();
        Element elem = null;
        try {
            elem = Xml.loadFile(context.getRealPath(servletRelativePath));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        try {
            updateWithOverrides(servletRelativePath, context, appPath, elem);
        } catch (Exception e) {
            e.printStackTrace();
            Log.error(Log.JEEVES, "Unable to read overrides for config.xml: "+e);
        }
        return elem;
    }


    /**
     * Loads a text file, compares each line to the textFile elements in the overrides file and if a match is found replaces that line with the line in the overrides
     *
     * Examples of use are to be able to update server.props using overrides and the sql data,create, etc... files with overrides so that the files can contain the defaults
     * and the specifics for a particular platform can be configured using overrides
     *
     * @param configFilePath The path to the files to be loaded and overriden.  IE /WEB-INF/server.prop
     * @param context the servlet context that is loaded (maybe null.  If null appPath is used to resolve configuration files like: /WEB-INF/configuration-overrides.xml
     * @param appPath The path to the webapplication root.  If servlet is null (and therefore getResource cannot be used, this path is used to file files)
     * @param reader a buffered reader opened to the file to be loaded.
     *
     * @return the array of lines in the file.
     * @throws JDOMException
     */
  public List<String> loadTextFileAndUpdate(String configFilePath, ServletContext context, String appPath, BufferedReader reader) throws IOException {
      ServletResourceLoader loader = new ServletResourceLoader(context, appPath);
     
        try {
            String resource = lookupOverrideParameter(context, appPath);
            return loadFileAndUpdate(loader, resource, configFilePath, reader);
        } catch (JDOMException e) {
            return loadFileAndUpdate(loader, null, configFilePath, reader);
        }

  }

    private List<String> loadFileAndUpdate(ResourceLoader loader, String overridesResource, String configFilePath,
            BufferedReader reader) throws IOException {
        Element overrides = loader.loadXmlResource(overridesResource);
    HashMap<Pattern, String> matches = new HashMap<Pattern, String>();

    Properties properties = new Properties();
    if (overrides != null) {
      properties = loadProperties(overrides);
      @SuppressWarnings("unchecked")
            List<Element> files = overrides.getChildren(TEXT_FILE_NODE_NAME);

      for (Element file : files) {
        String expectedfileName = file
            .getAttributeValue(FILE_NAME_ATT_NAME);

        if (Pattern.matches(expectedfileName, configFilePath)) {
          @SuppressWarnings("unchecked")
                    List<Element> updates = file.getChildren("update");
          for (Element element : updates) {
            matches.put(Pattern.compile(element
                .getAttributeValue("linePattern")), element
                .getTextTrim());
          }
        }
      }
    }

    ArrayList<String> al = new ArrayList<String>();

    String line = reader.readLine();
    try {
      while (line != null) {
        for (Map.Entry<Pattern, String> entry : matches.entrySet()) {
          Matcher matcher = entry.getKey().matcher(line);
          if (matcher.matches()) {
            String value = updatePropertiesInText(properties,
                entry.getValue());
            line = matcher.replaceFirst(value);
            break;
          }
        }
        al.add(line);
        line = reader.readLine();
      }
      return al;
    } finally {
      reader.close();
    }
  }

    @SuppressWarnings("unchecked")
    public void importSpringConfigurations(XmlBeanDefinitionReader reader, ConfigurableBeanFactory beanFactory, ServletContext servletContext, String appPath) throws JDOMException, IOException {
        String overridesResource = lookupOverrideParameter(servletContext, appPath);

        ResourceLoader loader = new ServletResourceLoader(servletContext, appPath);
       
        Element overrides = loader.loadXmlResource(overridesResource);
        if (overrides == null) {
            return;
        }

        Properties properties = loadProperties(overrides);

        for(Element e: (List<Element>) overrides.getChildren("spring")) {
            for (Element element : (List<Element>) e.getChildren("import")) {

                String importFile = element.getAttributeValue("file");
                importFile = updatePropertiesInText(properties, importFile);
               
                Log.info(Log.JEEVES, "ConfigurationOverrides: importing spring file into application context: "+importFile);
                File file = loader.resolveFile(importFile);
                if(file != null) {
                    Resource inputSource = new FileSystemResource(file);
                    reader.loadBeanDefinitions(inputSource);
                } else {
                    InputStream inputStream = loader.loadInputStream(importFile);
                    try {
                        Resource inputSource = new InputStreamResource(inputStream);
                        reader.loadBeanDefinitions(inputSource);
                    } finally {
                        IOUtils.closeQuietly(inputStream);
                    }
                }
            }
        }

       
    }

    public void postProcessSpringBeanFactory(ConfigurableListableBeanFactory beanFactory, ServletContext servletContext,
                                             String appPath) throws JDOMException, IOException {

        final SpringPropertyOverrides springPropertyOverrides = getSpringPropertyOverrides(servletContext, appPath);
        if (springPropertyOverrides == null) return;
        springPropertyOverrides.postProcessBeanFactory(beanFactory);
    }

    public void onSpringApplicationContextFinishedRefresh(ConfigurableListableBeanFactory beanFactory, ServletContext servletContext,
                                             String appPath) throws JDOMException, IOException {

        final SpringPropertyOverrides springPropertyOverrides = getSpringPropertyOverrides(servletContext, appPath);
        if (springPropertyOverrides == null) return;
        springPropertyOverrides.onFinishedRefresh(beanFactory);
    }

    @SuppressWarnings("unchecked")
    private synchronized SpringPropertyOverrides getSpringPropertyOverrides(ServletContext servletContext, String appPath) throws JDOMException, IOException {
        if (this.springPropertyOverrides == null) {
            String overridesResource = lookupOverrideParameter(servletContext, appPath);

            ResourceLoader loader = new ServletResourceLoader(servletContext, appPath);

            Element overrides = loader.loadXmlResource(overridesResource);
            if (overrides == null) {
                return null;
            }

            Properties properties = loadProperties(overrides);
            List<Element> updateEls = new ArrayList<Element>();
            List<Element> spring = new ArrayList<Element>(overrides.getChildren("spring"));
            for (Element el : spring) {
                for (Element element : (List<Element>) el.getChildren()) {
                    if (!element.getName().equals("import")) {
                        updateEls.add(element);
                    }

                }
            }
            this.springPropertyOverrides = new SpringPropertyOverrides(updateEls, properties);
        }
        return springPropertyOverrides;
    }

}
TOP

Related Classes of jeeves.server.overrides.ConfigurationOverrides

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.