1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
55
56
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
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
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
108
109
110
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
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 }