View Javadoc
1   /*
2    *    Copyright 2018-2022 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.mybatis.scripting.thymeleaf.processor;
17  
18  import java.lang.reflect.Array;
19  import java.util.Collection;
20  import java.util.function.UnaryOperator;
21  
22  import org.mybatis.scripting.thymeleaf.MyBatisBindingContext;
23  import org.thymeleaf.context.ITemplateContext;
24  import org.thymeleaf.engine.AttributeName;
25  import org.thymeleaf.engine.EngineEventUtils;
26  import org.thymeleaf.engine.IterationStatusVar;
27  import org.thymeleaf.model.IProcessableElementTag;
28  import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
29  import org.thymeleaf.processor.element.IElementTagStructureHandler;
30  import org.thymeleaf.standard.expression.IStandardExpression;
31  import org.thymeleaf.standard.expression.StandardExpressionExecutionContext;
32  import org.thymeleaf.templatemode.TemplateMode;
33  
34  /**
35   * The processor class for handling the {@code mb:p} tag. <br>
36   * This processor render bind variable(default: {@code #{…​}}) expression that can parsed data access library and
37   * register an iteration object to the bind variables.
38   *
39   * @author Kazuki Shimizu
40   *
41   * @version 1.0.0
42   */
43  public class MyBatisParamTagProcessor extends AbstractAttributeTagProcessor {
44  
45    private static final int PRECEDENCE = 1400;
46    private static final String ATTR_NAME = "p";
47  
48    private final StandardExpressionExecutionContext expressionExecutionContext;
49  
50    private UnaryOperator<String> bindVariableRender = BindVariableRender.BuiltIn.MYBATIS;
51  
52    /**
53     * Constructor that can be specified the template mode and dialect prefix.
54     *
55     * @param templateMode
56     *          A target template mode
57     * @param prefix
58     *          A target dialect prefix
59     */
60    public MyBatisParamTagProcessor(final TemplateMode templateMode, final String prefix) {
61      super(templateMode, prefix, null, false, ATTR_NAME, true, PRECEDENCE, true);
62      expressionExecutionContext = templateMode == TemplateMode.TEXT ? StandardExpressionExecutionContext.RESTRICTED
63          : StandardExpressionExecutionContext.NORMAL;
64    }
65  
66    /**
67     * Set a custom bind variable render function.<br>
68     * By default, render {@literal #{...}} format.
69     *
70     * @param bindVariableRender
71     *          a custom bind variable render function
72     *
73     * @since 1.0.2
74     */
75    public void setBindVariableRender(UnaryOperator<String> bindVariableRender) {
76      this.bindVariableRender = bindVariableRender;
77    }
78  
79    /**
80     * {@inheritDoc}
81     */
82    @Override
83    protected void doProcess(ITemplateContext context, IProcessableElementTag tag, AttributeName attributeName,
84        String attributeValue, IElementTagStructureHandler structureHandler) {
85      if (attributeValue.contains("${")) {
86        attributeValue = getExpressionEvaluatedText(context, tag, attributeName, attributeValue);
87      }
88  
89      Pair parameterAndOptionPair = Pair.parse(attributeValue, ',');
90      String parameterPath = parameterAndOptionPair.left;
91      String options = parameterAndOptionPair.right;
92  
93      Pair objectNameAndPropertyPathPair = Pair.parse(parameterPath, '.');
94      String objectName = objectNameAndPropertyPathPair.left;
95      String nestedPropertyPath = objectNameAndPropertyPathPair.right;
96  
97      String body;
98      String iterationObjectName = objectName + "Stat";
99      if (context.containsVariable(iterationObjectName)) {
100       MyBatisBindingContext bindingContext = MyBatisBindingContext.load(context);
101       IterationStatusVar iterationStatus = (IterationStatusVar) context.getVariable(iterationObjectName);
102       String iterationObjectVariableName = bindingContext.generateUniqueName(objectName, iterationStatus);
103       if (!bindingContext.containsCustomBindVariable(iterationObjectVariableName)) {
104         bindingContext.setCustomBindVariable(iterationObjectVariableName, iterationStatus.getCurrent());
105       }
106       if (nestedPropertyPath.isEmpty()) {
107         body = bindVariableRender.apply(iterationObjectVariableName + options);
108       } else {
109         Object value = getExpressionEvaluatedValue(context, tag, attributeName, parameterPath);
110         if (isCollectionOrArray(value)) {
111           body = generateCollectionBindVariables(value, iterationObjectVariableName + nestedPropertyPath, options);
112         } else {
113           body = bindVariableRender.apply(iterationObjectVariableName + nestedPropertyPath + options);
114         }
115       }
116     } else {
117       Object value = nestedPropertyPath.isEmpty() ? context.getVariable(objectName)
118           : getExpressionEvaluatedValue(context, tag, attributeName, parameterPath);
119       if (isCollectionOrArray(value)) {
120         body = generateCollectionBindVariables(value, parameterPath, options);
121       } else {
122         body = bindVariableRender.apply(attributeValue);
123       }
124     }
125     structureHandler.setBody(body, false);
126   }
127 
128   private Object getExpressionEvaluatedValue(ITemplateContext context, IProcessableElementTag tag,
129       AttributeName attributeName, String parameterValue) {
130     IStandardExpression expression = EngineEventUtils.computeAttributeExpression(context, tag, attributeName,
131         "${" + parameterValue + "}");
132     return expression.execute(context, this.expressionExecutionContext);
133   }
134 
135   private String getExpressionEvaluatedText(ITemplateContext context, IProcessableElementTag tag,
136       AttributeName attributeName, String parameterValue) {
137     IStandardExpression expression = EngineEventUtils.computeAttributeExpression(context, tag, attributeName,
138         "|" + parameterValue + "|");
139     return expression.execute(context, this.expressionExecutionContext).toString();
140   }
141 
142   private boolean isCollectionOrArray(Object value) {
143     return value != null && (Collection.class.isAssignableFrom(value.getClass()) || value.getClass().isArray());
144   }
145 
146   private String generateCollectionBindVariables(Object value, String parameterPath, String options) {
147     int size = value.getClass().isArray() ? Array.getLength(value) : ((Collection) value).size();
148     if (size == 0) {
149       return "null";
150     } else {
151       StringBuilder sb = new StringBuilder();
152       for (int i = 0; i < size; i++) {
153         if (i != 0) {
154           sb.append(", ");
155         }
156         sb.append(bindVariableRender.apply(parameterPath + "[" + i + "]" + options));
157       }
158       return sb.toString();
159     }
160   }
161 
162   private static class Pair {
163 
164     private final String left;
165     private final String right;
166 
167     private Pair(String left, String right) {
168       this.left = left;
169       this.right = right;
170     }
171 
172     private static Pair parse(String value, char separator) {
173       int separatorIndex = value.indexOf(separator);
174       String left;
175       String right;
176       if (separatorIndex == -1) {
177         left = value;
178         right = "";
179       } else {
180         left = value.substring(0, separatorIndex);
181         right = value.substring(separatorIndex);
182       }
183       return new Pair(left, right);
184     }
185 
186   }
187 
188 }