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