View Javadoc
1   /**
2    *    Copyright 2009-2015 the original author or authors.
3    *
4    *    Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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.apache.ibatis.builder.xml;
17  
18  import org.apache.ibatis.builder.BuilderException;
19  import org.apache.ibatis.builder.IncompleteElementException;
20  import org.apache.ibatis.builder.MapperBuilderAssistant;
21  import org.apache.ibatis.parsing.PropertyParser;
22  import org.apache.ibatis.parsing.XNode;
23  import org.apache.ibatis.session.Configuration;
24  import org.w3c.dom.Node;
25  import org.w3c.dom.NodeList;
26  
27  import java.util.Properties;
28  
29  /**
30   * @author Frank D. Martinez [mnesarco]
31   */
32  public class XMLIncludeTransformer {
33  
34    private final Configuration configuration;
35    private final MapperBuilderAssistant builderAssistant;
36  
37    public XMLIncludeTransformer(Configuration configuration, MapperBuilderAssistant builderAssistant) {
38      this.configuration = configuration;
39      this.builderAssistant = builderAssistant;
40    }
41  
42    public void applyIncludes(Node source) {
43      Properties variablesContext = new Properties();
44      Properties configurationVariables = configuration.getVariables();
45      if (configurationVariables != null) {
46        variablesContext.putAll(configurationVariables);
47      }
48      applyIncludes(source, variablesContext);
49    }
50  
51    /**
52     * Recursively apply includes through all SQL fragments.
53     * @param source Include node in DOM tree
54     * @param variablesContext Current context for static variables with values
55     */
56    private void applyIncludes(Node source, final Properties variablesContext) {
57      if (source.getNodeName().equals("include")) {
58        // new full context for included SQL - contains inherited context and new variables from current include node
59        Properties fullContext;
60  
61        String refid = getStringAttribute(source, "refid");
62        // replace variables in include refid value
63        refid = PropertyParser.parse(refid, variablesContext);
64        Node toInclude = findSqlFragment(refid);
65        Properties newVariablesContext = getVariablesContext(source, variablesContext);
66        if (!newVariablesContext.isEmpty()) {
67          // merge contexts
68          fullContext = new Properties();
69          fullContext.putAll(variablesContext);
70          fullContext.putAll(newVariablesContext);
71        } else {
72          // no new context - use inherited fully
73          fullContext = variablesContext;
74        }
75        applyIncludes(toInclude, fullContext);
76        if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
77          toInclude = source.getOwnerDocument().importNode(toInclude, true);
78        }
79        source.getParentNode().replaceChild(toInclude, source);
80        while (toInclude.hasChildNodes()) {
81          toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
82        }
83        toInclude.getParentNode().removeChild(toInclude);
84      } else if (source.getNodeType() == Node.ELEMENT_NODE) {
85        NodeList children = source.getChildNodes();
86        for (int i=0; i<children.getLength(); i++) {
87          applyIncludes(children.item(i), variablesContext);
88        }
89      } else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {
90        // replace variables in all attribute values
91        source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
92      } else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {
93        // replace variables ins all text nodes
94        source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
95      }
96    }
97  
98    private Node findSqlFragment(String refid) {
99      refid = builderAssistant.applyCurrentNamespace(refid, true);
100     try {
101       XNode nodeToInclude = configuration.getSqlFragments().get(refid);
102       return nodeToInclude.getNode().cloneNode(true);
103     } catch (IllegalArgumentException e) {
104       throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);
105     }
106   }
107 
108   private String getStringAttribute(Node node, String name) {
109     return node.getAttributes().getNamedItem(name).getNodeValue();
110   }
111 
112   /**
113    * Read placholders and their values from include node definition. 
114    * @param node Include node instance
115    * @param inheritedVariablesContext Current context used for replace variables in new variables values
116    * @return variables context from include instance (no inherited values)
117    */
118   private Properties getVariablesContext(Node node, Properties inheritedVariablesContext) {
119     Properties variablesContext = new Properties();
120     NodeList children = node.getChildNodes();
121     for (int i = 0; i < children.getLength(); i++) {
122       Node n = children.item(i);
123       if (n.getNodeType() == Node.ELEMENT_NODE) {
124         String name = getStringAttribute(n, "name");
125         String value = getStringAttribute(n, "value");
126         // Replace variables inside
127         value = PropertyParser.parse(value, inheritedVariablesContext);
128         // Push new value
129         Object originalValue = variablesContext.put(name, value);
130         if (originalValue != null) {
131           throw new BuilderException("Variable " + name + " defined twice in the same include definition");
132         }
133       }
134     }
135     return variablesContext;
136   }
137   
138 }