1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.scripting.xmltags;
17
18 import java.util.Map;
19 import java.util.Optional;
20
21 import org.apache.ibatis.parsing.GenericTokenParser;
22 import org.apache.ibatis.session.Configuration;
23
24
25
26
27 public class ForEachSqlNode implements SqlNode {
28 public static final String ITEM_PREFIX = "__frch_";
29
30 private final ExpressionEvaluator evaluator;
31 private final String collectionExpression;
32 private final Boolean nullable;
33 private final SqlNode contents;
34 private final String open;
35 private final String close;
36 private final String separator;
37 private final String item;
38 private final String index;
39 private final Configuration configuration;
40
41
42
43
44
45 @Deprecated
46 public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index,
47 String item, String open, String close, String separator) {
48 this(configuration, contents, collectionExpression, null, index, item, open, close, separator);
49 }
50
51
52
53
54 public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, Boolean nullable,
55 String index, String item, String open, String close, String separator) {
56 this.evaluator = new ExpressionEvaluator();
57 this.collectionExpression = collectionExpression;
58 this.nullable = nullable;
59 this.contents = contents;
60 this.open = open;
61 this.close = close;
62 this.separator = separator;
63 this.index = index;
64 this.item = item;
65 this.configuration = configuration;
66 }
67
68 @Override
69 public boolean apply(DynamicContext context) {
70 Map<String, Object> bindings = context.getBindings();
71 final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings,
72 Optional.ofNullable(nullable).orElseGet(configuration::isNullableOnForEach));
73 if (iterable == null || !iterable.iterator().hasNext()) {
74 return true;
75 }
76 boolean first = true;
77 applyOpen(context);
78 int i = 0;
79 for (Object o : iterable) {
80 DynamicContext oldContext = context;
81 if (first || separator == null) {
82 context = new PrefixedContext(context, "");
83 } else {
84 context = new PrefixedContext(context, separator);
85 }
86 int uniqueNumber = context.getUniqueNumber();
87
88 if (o instanceof Map.Entry) {
89 @SuppressWarnings("unchecked")
90 Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
91 applyIndex(context, mapEntry.getKey(), uniqueNumber);
92 applyItem(context, mapEntry.getValue(), uniqueNumber);
93 } else {
94 applyIndex(context, i, uniqueNumber);
95 applyItem(context, o, uniqueNumber);
96 }
97 contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
98 if (first) {
99 first = !((PrefixedContext) context).isPrefixApplied();
100 }
101 context = oldContext;
102 i++;
103 }
104 applyClose(context);
105 context.getBindings().remove(item);
106 context.getBindings().remove(index);
107 return true;
108 }
109
110 private void applyIndex(DynamicContext context, Object o, int i) {
111 if (index != null) {
112 context.bind(index, o);
113 context.bind(itemizeItem(index, i), o);
114 }
115 }
116
117 private void applyItem(DynamicContext context, Object o, int i) {
118 if (item != null) {
119 context.bind(item, o);
120 context.bind(itemizeItem(item, i), o);
121 }
122 }
123
124 private void applyOpen(DynamicContext context) {
125 if (open != null) {
126 context.appendSql(open);
127 }
128 }
129
130 private void applyClose(DynamicContext context) {
131 if (close != null) {
132 context.appendSql(close);
133 }
134 }
135
136 private static String itemizeItem(String item, int i) {
137 return ITEM_PREFIX + item + "_" + i;
138 }
139
140 private static class FilteredDynamicContext extends DynamicContext {
141 private final DynamicContext delegate;
142 private final int index;
143 private final String itemIndex;
144 private final String item;
145
146 public FilteredDynamicContext(Configuration configuration, DynamicContext delegate, String itemIndex, String item,
147 int i) {
148 super(configuration, null);
149 this.delegate = delegate;
150 this.index = i;
151 this.itemIndex = itemIndex;
152 this.item = item;
153 }
154
155 @Override
156 public Map<String, Object> getBindings() {
157 return delegate.getBindings();
158 }
159
160 @Override
161 public void bind(String name, Object value) {
162 delegate.bind(name, value);
163 }
164
165 @Override
166 public String getSql() {
167 return delegate.getSql();
168 }
169
170 @Override
171 public void appendSql(String sql) {
172 GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> {
173 String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
174 if (itemIndex != null && newContent.equals(content)) {
175 newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
176 }
177 return "#{" + newContent + "}";
178 });
179
180 delegate.appendSql(parser.parse(sql));
181 }
182
183 @Override
184 public int getUniqueNumber() {
185 return delegate.getUniqueNumber();
186 }
187
188 }
189
190 private class PrefixedContext extends DynamicContext {
191 private final DynamicContext delegate;
192 private final String prefix;
193 private boolean prefixApplied;
194
195 public PrefixedContext(DynamicContext delegate, String prefix) {
196 super(configuration, null);
197 this.delegate = delegate;
198 this.prefix = prefix;
199 this.prefixApplied = false;
200 }
201
202 public boolean isPrefixApplied() {
203 return prefixApplied;
204 }
205
206 @Override
207 public Map<String, Object> getBindings() {
208 return delegate.getBindings();
209 }
210
211 @Override
212 public void bind(String name, Object value) {
213 delegate.bind(name, value);
214 }
215
216 @Override
217 public void appendSql(String sql) {
218 if (!prefixApplied && sql != null && sql.trim().length() > 0) {
219 delegate.appendSql(prefix);
220 prefixApplied = true;
221 }
222 delegate.appendSql(sql);
223 }
224
225 @Override
226 public String getSql() {
227 return delegate.getSql();
228 }
229
230 @Override
231 public int getUniqueNumber() {
232 return delegate.getUniqueNumber();
233 }
234 }
235
236 }