View Javadoc
1   /*
2    *    Copyright 2012-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.velocity;
17  
18  import java.io.IOException;
19  import java.io.StringWriter;
20  import java.io.Writer;
21  import java.util.Iterator;
22  
23  import org.apache.velocity.context.InternalContextAdapter;
24  import org.apache.velocity.exception.TemplateInitException;
25  import org.apache.velocity.exception.VelocityException;
26  import org.apache.velocity.runtime.RuntimeServices;
27  import org.apache.velocity.runtime.directive.StopCommand;
28  import org.apache.velocity.runtime.parser.node.ASTReference;
29  import org.apache.velocity.runtime.parser.node.ASTStringLiteral;
30  import org.apache.velocity.runtime.parser.node.Node;
31  import org.apache.velocity.runtime.parser.node.ParserTreeConstants;
32  import org.apache.velocity.util.introspection.Info;
33  
34  /**
35   * #in($collection $item COLUMN).
36   */
37  public class InDirective extends RepeatDirective {
38  
39    /**
40     * Immutable fields
41     */
42    private String var;
43  
44    private String open = "(";
45  
46    private String close = ")";
47  
48    private String separator = ", ";
49  
50    private String column = "";
51  
52    @Override
53    public String getName() {
54      return "in";
55    }
56  
57    @Override
58    public void init(RuntimeServices rs, InternalContextAdapter context, Node node) {
59      super.init(rs, context, node);
60      final int n = node.jjtGetNumChildren() - 1;
61      for (int i = 1; i < n; i++) {
62        Node child = node.jjtGetChild(i);
63        if (i == 1) {
64          if (child.getType() == ParserTreeConstants.JJTREFERENCE) {
65            this.var = ((ASTReference) child).getRootString();
66          } else {
67            throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn());
68          }
69        } else if (child.getType() == ParserTreeConstants.JJTSTRINGLITERAL) {
70          String value = (String) ((ASTStringLiteral) child).value(context);
71          if (i == 2) {
72            this.column = value;
73          }
74        } else {
75          throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn());
76        }
77      }
78      this.uberInfo = new Info(this.getTemplateName(), getLine(), getColumn());
79    }
80  
81    @Override
82    public boolean render(InternalContextAdapter context, Writer writer, Node node) throws IOException {
83      Object listObject = node.jjtGetChild(0).value(context);
84      if (listObject == null) {
85        return false;
86      }
87  
88      Iterator<?> iterator = null;
89  
90      try {
91        iterator = this.rsvc.getUberspect().getIterator(listObject, this.uberInfo);
92      } catch (RuntimeException e) {
93        throw e;
94      } catch (Exception ee) {
95        String msg = "Error getting iterator for #in at " + this.uberInfo;
96        this.rsvc.getLog().error(msg, ee);
97        throw new VelocityException(msg, ee);
98      }
99  
100     if (iterator == null) {
101       throw new VelocityException("Invalid collection");
102     }
103 
104     int counter = 0;
105     Object o = context.get(this.var);
106 
107     ParameterMappingCollector collector = (ParameterMappingCollector) context
108         .get(SQLScriptSource.MAPPING_COLLECTOR_KEY);
109     String savedItemKey = collector.getItemKey();
110     collector.setItemKey(this.var);
111     RepeatScope foreach = new RepeatScope(this, context.get(getName()), this.var);
112     context.put(getName(), foreach);
113 
114     NullHolderContext nullHolderContext = null;
115     Object value = null;
116     StringWriter buffer = new StringWriter();
117 
118     while (iterator.hasNext()) {
119 
120       if (counter % MAX_IN_CLAUSE_SIZE == 0) {
121         buffer.append(this.open); // Group begins
122         buffer.append(this.column);
123         buffer.append(" IN ");
124         buffer.append(this.open); // In starts
125       }
126 
127       value = iterator.next();
128       put(context, this.var, value);
129       foreach.index++;
130       foreach.hasNext = iterator.hasNext();
131 
132       try {
133         if (value == null) {
134           if (nullHolderContext == null) {
135             nullHolderContext = new NullHolderContext(this.var, context);
136           }
137           node.jjtGetChild(node.jjtGetNumChildren() - 1).render(nullHolderContext, buffer);
138         } else {
139           node.jjtGetChild(node.jjtGetNumChildren() - 1).render(context, buffer);
140         }
141       } catch (StopCommand stop) {
142         if (stop.isFor(this)) {
143           break;
144         }
145         clean(context, o, collector, savedItemKey);
146         // close does not perform any action and this is here
147         // to avoid eclipse reporting possible leak.
148         buffer.close();
149         throw stop;
150       }
151       counter++;
152 
153       if ((counter > 0 && counter % MAX_IN_CLAUSE_SIZE == 0) || !iterator.hasNext()) {
154         buffer.append(this.close); // In ends
155         buffer.append(this.close); // Group ends
156         if (iterator.hasNext()) {
157           buffer.append(" OR ");
158         }
159       } else if (iterator.hasNext()) {
160         buffer.append(this.separator);
161       }
162 
163     }
164     String content = buffer.toString().trim();
165     if (!"".equals(content)) {
166       writer.append(this.open);
167       writer.append(content);
168       writer.append(this.close);
169     } else {
170       writer.append(this.open);
171       writer.append(this.open);
172       writer.append(this.column);
173       writer.append(" NOT IN ");
174       writer.append(this.open);
175       writer.append(" NULL ");
176       writer.append(this.close);
177       writer.append(this.close);
178       writer.append(this.close);
179     }
180     clean(context, o, collector, savedItemKey);
181     return true;
182   }
183 
184   @Override
185   public int getType() {
186     return BLOCK;
187   }
188 
189 }