1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.shardingsphere.infra.expr.groovy;
19
20 import com.github.benmanes.caffeine.cache.Cache;
21 import com.github.benmanes.caffeine.cache.Caffeine;
22 import com.google.common.base.Strings;
23 import com.google.common.collect.Sets;
24 import groovy.lang.Closure;
25 import groovy.lang.GString;
26 import groovy.lang.GroovyShell;
27 import groovy.lang.Script;
28 import groovy.util.Expando;
29 import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
30 import org.apache.shardingsphere.infra.config.props.ConfigurationPropertyKey;
31 import org.apache.shardingsphere.infra.expr.core.GroovyUtils;
32 import org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser;
33
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.LinkedHashSet;
39 import java.util.LinkedList;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Properties;
43 import java.util.Set;
44 import java.util.stream.Collectors;
45
46
47
48
49 public final class GroovyInlineExpressionParser implements InlineExpressionParser {
50
51 private static final String INLINE_EXPRESSION_KEY = "inlineExpression";
52
53 private static final GroovyShell SHELL = new GroovyShell();
54
55 private static volatile long currentCacheSize = Long.parseLong(ConfigurationPropertyKey.GROOVY_INLINE_EXPRESSION_PARSING_CACHE_MAX_SIZE.getDefaultValue());
56
57 private static volatile Cache<String, Script> scriptCache = Caffeine.newBuilder().maximumSize(currentCacheSize).softValues().build();
58
59 private String inlineExpression;
60
61 @Override
62 public void init(final Properties props) {
63 inlineExpression = props.getProperty(INLINE_EXPRESSION_KEY);
64 long maxCacheSize = new ConfigurationProperties(props).getValue(ConfigurationPropertyKey.GROOVY_INLINE_EXPRESSION_PARSING_CACHE_MAX_SIZE);
65 updateMaxCacheSize(maxCacheSize);
66 }
67
68 private static void updateMaxCacheSize(final long newMaxCacheSize) {
69 if (newMaxCacheSize == currentCacheSize) {
70 return;
71 }
72 synchronized (GroovyInlineExpressionParser.class) {
73 if (newMaxCacheSize != currentCacheSize) {
74 scriptCache = Caffeine.newBuilder().maximumSize(newMaxCacheSize).softValues().build();
75 currentCacheSize = newMaxCacheSize;
76 }
77 }
78 }
79
80 @Override
81 public String handlePlaceHolder() {
82 return handlePlaceHolder(inlineExpression);
83 }
84
85
86
87
88
89
90
91 private String handlePlaceHolder(final String inlineExpression) {
92 return inlineExpression.contains("$->{") ? inlineExpression.replaceAll("\\$->\\{", "\\${") : inlineExpression;
93 }
94
95
96
97
98
99
100 @Override
101 public List<String> splitAndEvaluate() {
102 if (Strings.isNullOrEmpty(inlineExpression)) {
103 return Collections.emptyList();
104 }
105 if (isConstantExpression(inlineExpression)) {
106 return Arrays.stream(inlineExpression.split("\\s*,\\s*")).map(String::trim).filter(each -> !each.isEmpty()).collect(Collectors.toList());
107 }
108 return flatten(evaluate(GroovyUtils.split(handlePlaceHolder(inlineExpression))));
109 }
110
111
112
113
114
115
116 @Override
117 public String evaluateWithArgs(final Map<String, Comparable<?>> map) {
118 if (isConstantExpression(inlineExpression)) {
119 return inlineExpression;
120 }
121 Object scriptResult = evaluate("{it -> \"" + handlePlaceHolder(inlineExpression) + "\"}");
122 if (scriptResult instanceof Closure) {
123 Closure<?> result = ((Closure<?>) scriptResult).rehydrate(new Expando(), null, null);
124 result.setResolveStrategy(Closure.DELEGATE_ONLY);
125 map.forEach(result::setProperty);
126 return result.call().toString();
127 }
128 return scriptResult.toString();
129 }
130
131 private List<Object> evaluate(final List<String> inlineExpressions) {
132 List<Object> result = new ArrayList<>(inlineExpressions.size());
133 for (String each : inlineExpressions) {
134 StringBuilder expression = new StringBuilder(handlePlaceHolder(each));
135 if (!each.startsWith("\"")) {
136 expression.insert(0, '"');
137 }
138 if (!each.endsWith("\"")) {
139 expression.append('"');
140 }
141 result.add(evaluate(expression.toString()));
142 }
143 return result;
144 }
145
146 private Object evaluate(final String expression) {
147 if (isConstantExpression(expression)) {
148 return expression.replaceAll("^\"|\"$", "");
149 }
150 Script script = scriptCache.get(expression, SHELL::parse);
151 return null == script ? expression : script.run();
152 }
153
154 private boolean isConstantExpression(final String expression) {
155 return Strings.isNullOrEmpty(expression) || !isDynamicExpression(expression);
156 }
157
158 private boolean isDynamicExpression(final String expression) {
159 return expression.contains("${") || expression.contains("$->{");
160 }
161
162 private List<String> flatten(final List<Object> segments) {
163 List<String> result = new ArrayList<>();
164 for (Object each : segments) {
165 if (each instanceof GString) {
166 result.addAll(assemblyCartesianSegments((GString) each));
167 } else if (each instanceof Script) {
168 result.addAll(flattenScript((Script) each));
169 } else {
170 result.add(each.toString());
171 }
172 }
173 return result;
174 }
175
176 private List<String> assemblyCartesianSegments(final GString segment) {
177 Set<List<String>> cartesianValues = getCartesianValues(segment);
178 List<String> result = new ArrayList<>(cartesianValues.size());
179 for (List<String> each : cartesianValues) {
180 result.add(assemblySegment(each, segment));
181 }
182 return result;
183 }
184
185 @SuppressWarnings("unchecked")
186 private Set<List<String>> getCartesianValues(final GString segment) {
187 List<Set<String>> result = new ArrayList<>(segment.getValues().length);
188 for (Object each : segment.getValues()) {
189 if (null == each) {
190 continue;
191 }
192 if (each instanceof Collection) {
193 result.add(((Collection<Object>) each).stream().map(Object::toString).collect(Collectors.toCollection(LinkedHashSet::new)));
194 } else {
195 result.add(Sets.newHashSet(each.toString()));
196 }
197 }
198 return Sets.cartesianProduct(result);
199 }
200
201 private String assemblySegment(final List<String> cartesianValue, final GString segment) {
202 StringBuilder result = new StringBuilder();
203 for (int i = 0; i < segment.getStrings().length; i++) {
204 result.append(segment.getStrings()[i]);
205 if (i < cartesianValue.size()) {
206 result.append(cartesianValue.get(i));
207 }
208 }
209 return result.toString();
210 }
211
212 private Collection<String> flattenScript(final Script script) {
213 Collection<String> result = new LinkedList<>();
214 Object scriptResult = script.run();
215 if (scriptResult instanceof Iterable) {
216 for (Object item : (Iterable<?>) scriptResult) {
217 result.add(item.toString());
218 }
219 } else {
220 result.add(scriptResult.toString());
221 }
222 return result;
223 }
224
225 @Override
226 public String getType() {
227 return "GROOVY";
228 }
229 }