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 = ExpressionEvaluator.INSTANCE;
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.collectionExpression = collectionExpression;
57 this.nullable = nullable;
58 this.contents = contents;
59 this.open = open;
60 this.close = close;
61 this.separator = separator;
62 this.index = index;
63 this.item = item;
64 this.configuration = configuration;
65 }
66
67 @Override
68 public boolean apply(DynamicContext context) {
69 Map<String, Object> bindings = context.getBindings();
70 final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings,
71 Optional.ofNullable(nullable).orElseGet(configuration::isNullableOnForEach));
72 if (iterable == null || !iterable.iterator().hasNext()) {
73 return true;
74 }
75 boolean first = true;
76 applyOpen(context);
77 int i = 0;
78 for (Object o : iterable) {
79 DynamicContext oldContext = context;
80 if (first || separator == null) {
81 context = new PrefixedContext(context, "");
82 } else {
83 context = new PrefixedContext(context, separator);
84 }
85 int uniqueNumber = context.getUniqueNumber();
86
87 if (o instanceof Map.Entry) {
88 @SuppressWarnings("unchecked")
89 Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
90 applyIndex(context, mapEntry.getKey(), uniqueNumber);
91 applyItem(context, mapEntry.getValue(), uniqueNumber);
92 } else {
93 applyIndex(context, i, uniqueNumber);
94 applyItem(context, o, uniqueNumber);
95 }
96 contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
97 if (first) {
98 first = !((PrefixedContext) context).isPrefixApplied();
99 }
100 context = oldContext;
101 i++;
102 }
103 applyClose(context);
104 context.getBindings().remove(item);
105 context.getBindings().remove(index);
106 return true;
107 }
108
109 private void applyIndex(DynamicContext context, Object o, int i) {
110 if (index != null) {
111 context.bind(index, o);
112 context.bind(itemizeItem(index, i), o);
113 }
114 }
115
116 private void applyItem(DynamicContext context, Object o, int i) {
117 if (item != null) {
118 context.bind(item, o);
119 context.bind(itemizeItem(item, i), o);
120 }
121 }
122
123 private void applyOpen(DynamicContext context) {
124 if (open != null) {
125 context.appendSql(open);
126 }
127 }
128
129 private void applyClose(DynamicContext context) {
130 if (close != null) {
131 context.appendSql(close);
132 }
133 }
134
135 private static String itemizeItem(String item, int i) {
136 return ITEM_PREFIX + item + "_" + i;
137 }
138
139 private static class FilteredDynamicContext extends DynamicContext {
140 private final DynamicContext delegate;
141 private final int index;
142 private final String itemIndex;
143 private final String item;
144
145 public FilteredDynamicContext(Configuration configuration, DynamicContext delegate, String itemIndex, String item,
146 int i) {
147 super(configuration, null);
148 this.delegate = delegate;
149 this.index = i;
150 this.itemIndex = itemIndex;
151 this.item = item;
152 }
153
154 @Override
155 public Map<String, Object> getBindings() {
156 return delegate.getBindings();
157 }
158
159 @Override
160 public void bind(String name, Object value) {
161 delegate.bind(name, value);
162 }
163
164 @Override
165 public String getSql() {
166 return delegate.getSql();
167 }
168
169 @Override
170 public void appendSql(String sql) {
171 GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> {
172 String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
173 if (itemIndex != null && newContent.equals(content)) {
174 newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
175 }
176 return "#{" + newContent + "}";
177 });
178
179 delegate.appendSql(parser.parse(sql));
180 }
181
182 @Override
183 public int getUniqueNumber() {
184 return delegate.getUniqueNumber();
185 }
186
187 }
188
189 private class PrefixedContext extends DynamicContext {
190 private final DynamicContext delegate;
191 private final String prefix;
192 private boolean prefixApplied;
193
194 public PrefixedContext(DynamicContext delegate, String prefix) {
195 super(configuration, null);
196 this.delegate = delegate;
197 this.prefix = prefix;
198 this.prefixApplied = false;
199 }
200
201 public boolean isPrefixApplied() {
202 return prefixApplied;
203 }
204
205 @Override
206 public Map<String, Object> getBindings() {
207 return delegate.getBindings();
208 }
209
210 @Override
211 public void bind(String name, Object value) {
212 delegate.bind(name, value);
213 }
214
215 @Override
216 public void appendSql(String sql) {
217 if (!prefixApplied && sql != null && sql.trim().length() > 0) {
218 delegate.appendSql(prefix);
219 prefixApplied = true;
220 }
221 delegate.appendSql(sql);
222 }
223
224 @Override
225 public String getSql() {
226 return delegate.getSql();
227 }
228
229 @Override
230 public int getUniqueNumber() {
231 return delegate.getUniqueNumber();
232 }
233 }
234
235 }