View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shardingsphere.infra.expr.groovy;
19  
20  import com.google.common.base.Strings;
21  import com.google.common.collect.Sets;
22  import groovy.lang.Closure;
23  import groovy.lang.GString;
24  import groovy.lang.GroovyShell;
25  import groovy.lang.Script;
26  import groovy.util.Expando;
27  import org.apache.shardingsphere.infra.expr.spi.InlineExpressionParser;
28  import org.apache.shardingsphere.infra.util.groovy.GroovyUtils;
29  
30  import java.util.ArrayList;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.LinkedHashSet;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Properties;
37  import java.util.Set;
38  import java.util.concurrent.ConcurrentHashMap;
39  import java.util.stream.Collectors;
40  
41  /**
42   * Groovy inline expression parser.
43   */
44  public final class GroovyInlineExpressionParser implements InlineExpressionParser {
45      
46      private static final String INLINE_EXPRESSION_KEY = "inlineExpression";
47      
48      private static final Map<String, Script> SCRIPTS = new ConcurrentHashMap<>();
49      
50      private static final GroovyShell SHELL = new GroovyShell();
51      
52      private String inlineExpression;
53      
54      @Override
55      public void init(final Properties props) {
56          inlineExpression = props.getProperty(INLINE_EXPRESSION_KEY);
57      }
58      
59      @Override
60      public String handlePlaceHolder() {
61          return handlePlaceHolder(inlineExpression);
62      }
63      
64      /**
65       * Replace all inline expression placeholders.
66       *
67       * @param inlineExpression inline expression with {@code $->}
68       * @return result inline expression with {@code $}
69       */
70      private String handlePlaceHolder(final String inlineExpression) {
71          return inlineExpression.contains("$->{") ? inlineExpression.replaceAll("\\$->\\{", "\\$\\{") : inlineExpression;
72      }
73      
74      /**
75       * Split and Evaluate inline expression. This function will replace all inline expression placeholders.
76       *
77       * @return result inline expression with {@code $}
78       */
79      @Override
80      public List<String> splitAndEvaluate() {
81          return Strings.isNullOrEmpty(inlineExpression) ? Collections.emptyList() : flatten(evaluate(GroovyUtils.split(handlePlaceHolder(inlineExpression))));
82      }
83      
84      /**
85       * Turn inline expression into Groovy Closure. This function will replace all inline expression placeholders.
86       * For compatibility reasons, it does not check whether the unit of the input parameter map is null.
87       * @return The result of the Groovy Closure pattern.
88       */
89      @Override
90      public String evaluateWithArgs(final Map<String, Comparable<?>> map) {
91          Closure<?> result = ((Closure<?>) evaluate("{it -> \"" + handlePlaceHolder(inlineExpression) + "\"}")).rehydrate(new Expando(), null, null);
92          result.setResolveStrategy(Closure.DELEGATE_ONLY);
93          map.forEach(result::setProperty);
94          return result.call().toString();
95      }
96      
97      private List<Object> evaluate(final List<String> inlineExpressions) {
98          List<Object> result = new ArrayList<>(inlineExpressions.size());
99          for (String each : inlineExpressions) {
100             StringBuilder expression = new StringBuilder(handlePlaceHolder(each));
101             if (!each.startsWith("\"")) {
102                 expression.insert(0, '"');
103             }
104             if (!each.endsWith("\"")) {
105                 expression.append('"');
106             }
107             result.add(evaluate(expression.toString()));
108         }
109         return result;
110     }
111     
112     private Object evaluate(final String expression) {
113         Script script;
114         if (SCRIPTS.containsKey(expression)) {
115             script = SCRIPTS.get(expression);
116         } else {
117             script = SHELL.parse(expression);
118             SCRIPTS.put(expression, script);
119         }
120         return script.run();
121     }
122     
123     private List<String> flatten(final List<Object> segments) {
124         List<String> result = new ArrayList<>();
125         for (Object each : segments) {
126             if (each instanceof GString) {
127                 result.addAll(assemblyCartesianSegments((GString) each));
128             } else {
129                 result.add(each.toString());
130             }
131         }
132         return result;
133     }
134     
135     private List<String> assemblyCartesianSegments(final GString segment) {
136         Set<List<String>> cartesianValues = getCartesianValues(segment);
137         List<String> result = new ArrayList<>(cartesianValues.size());
138         for (List<String> each : cartesianValues) {
139             result.add(assemblySegment(each, segment));
140         }
141         return result;
142     }
143     
144     @SuppressWarnings("unchecked")
145     private Set<List<String>> getCartesianValues(final GString segment) {
146         List<Set<String>> result = new ArrayList<>(segment.getValues().length);
147         for (Object each : segment.getValues()) {
148             if (null == each) {
149                 continue;
150             }
151             if (each instanceof Collection) {
152                 result.add(((Collection<Object>) each).stream().map(Object::toString).collect(Collectors.toCollection(LinkedHashSet::new)));
153             } else {
154                 result.add(Sets.newHashSet(each.toString()));
155             }
156         }
157         return Sets.cartesianProduct(result);
158     }
159     
160     private String assemblySegment(final List<String> cartesianValue, final GString segment) {
161         StringBuilder result = new StringBuilder();
162         for (int i = 0; i < segment.getStrings().length; i++) {
163             result.append(segment.getStrings()[i]);
164             if (i < cartesianValue.size()) {
165                 result.append(cartesianValue.get(i));
166             }
167         }
168         return result.toString();
169     }
170     
171     @Override
172     public String getType() {
173         return "GROOVY";
174     }
175 }