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
59
60
61 private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
62 if ("include".equals(source.getNodeName())) {
63 Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
64 Properties toIncludeContext = getVariablesContext(source, variablesContext);
65 applyIncludes(toInclude, toIncludeContext, true);
66 if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
67 toInclude = source.getOwnerDocument().importNode(toInclude, true);
68 }
69 source.getParentNode().replaceChild(toInclude, source);
70 while (toInclude.hasChildNodes()) {
71 toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
72 }
73 toInclude.getParentNode().removeChild(toInclude);
74 } else if (source.getNodeType() == Node.ELEMENT_NODE) {
75 if (included && !variablesContext.isEmpty()) {
76
77 NamedNodeMap attributes = source.getAttributes();
78 for (int i = 0; i < attributes.getLength(); i++) {
79 Node attr = attributes.item(i);
80 attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
81 }
82 }
83 NodeList children = source.getChildNodes();
84 for (int i = 0; i < children.getLength(); i++) {
85 applyIncludes(children.item(i), variablesContext, included);
86 }
87 } else if (included && (source.getNodeType() == Node.TEXT_NODE || source.getNodeType() == Node.CDATA_SECTION_NODE)
88 && !variablesContext.isEmpty()) {
89
90 source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
91 }
92 }
93
94 private Node findSqlFragment(String refid, Properties variables) {
95 refid = PropertyParser.parse(refid, variables);
96 refid = builderAssistant.applyCurrentNamespace(refid, true);
97 try {
98 XNode nodeToInclude = configuration.getSqlFragments().get(refid);
99 return nodeToInclude.getNode().cloneNode(true);
100 } catch (IllegalArgumentException e) {
101 throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);
102 }
103 }
104
105 private String getStringAttribute(Node node, String name) {
106 return node.getAttributes().getNamedItem(name).getNodeValue();
107 }
108
109
110
111
112
113
114
115
116
117
118 private Properties getVariablesContext(Node node, Properties inheritedVariablesContext) {
119 Map<String, String> declaredProperties = null;
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
126 String value = PropertyParser.parse(getStringAttribute(n, "value"), inheritedVariablesContext);
127 if (declaredProperties == null) {
128 declaredProperties = new HashMap<>();
129 }
130 if (declaredProperties.put(name, value) != null) {
131 throw new BuilderException("Variable " + name + " defined twice in the same include definition");
132 }
133 }
134 }
135 if (declaredProperties == null) {
136 return inheritedVariablesContext;
137 } else {
138 Properties newProperties = new Properties();
139 newProperties.putAll(inheritedVariablesContext);
140 newProperties.putAll(declaredProperties);
141 return newProperties;
142 }
143 }
144 }