CAY-2345 Own template renderer as a replacement for Velocity
  - final version of render with cache and parser pool


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/54feb976
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/54feb976
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/54feb976

Branch: refs/heads/master
Commit: 54feb9762ef0356edc20def754bca65749451bec
Parents: e45f41f
Author: Nikita Timofeev <stari...@gmail.com>
Authored: Wed Aug 9 16:41:21 2017 +0300
Committer: Nikita Timofeev <stari...@gmail.com>
Committed: Wed Aug 16 18:29:54 2017 +0300

----------------------------------------------------------------------
 .../template/CayenneSQLTemplateProcessor.java   | 16 +++--
 .../org/apache/cayenne/template/Context.java    | 16 ++++-
 .../cayenne/template/TemplateParserPool.java    | 60 ++++++++++++++++
 .../apache/cayenne/template/directive/Bind.java | 21 +++---
 .../cayenne/template/directive/BindEqual.java   |  6 +-
 .../template/directive/BindNotEqual.java        |  6 +-
 .../template/directive/BindObjectEqual.java     | 32 ++++-----
 .../template/directive/BindObjectNotEqual.java  | 12 ++--
 .../cayenne/template/directive/Directive.java   |  2 +-
 .../cayenne/template/directive/Result.java      | 16 ++---
 .../cayenne/template/parser/ASTArray.java       |  7 +-
 .../cayenne/template/parser/ASTBlock.java       |  9 +--
 .../cayenne/template/parser/ASTDirective.java   |  6 +-
 .../cayenne/template/parser/ASTExpression.java  | 13 +++-
 .../cayenne/template/parser/ASTIfElse.java      |  7 +-
 .../cayenne/template/parser/ASTMethod.java      | 15 ++--
 .../cayenne/template/parser/ASTVariable.java    | 14 ++--
 .../cayenne/template/parser/ExpressionNode.java |  2 +
 .../parser/JJTSQLTemplateParserState.java       | 20 +++---
 .../cayenne/template/parser/JavaCharStream.java | 23 +++++--
 .../apache/cayenne/template/parser/Node.java    | 14 +---
 .../template/parser/SQLTemplateParser.java      |  9 +--
 .../cayenne/template/parser/ScalarNode.java     | 10 ++-
 .../cayenne/template/parser/SimpleNode.java     | 12 ++--
 .../template/parser/SQLTemplateParser.jjt       | 12 ++--
 .../org/apache/cayenne/query/SQLTemplateIT.java |  4 --
 .../template/TemplateParserPoolTest.java        | 72 ++++++++++++++++++++
 .../template/parser/SQLTemplateParserTest.java  | 10 +--
 28 files changed, 302 insertions(+), 144 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
index 0352417..2d80523 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/CayenneSQLTemplateProcessor.java
@@ -19,8 +19,7 @@
 
 package org.apache.cayenne.template;
 
-import java.io.BufferedReader;
-import java.io.StringReader;
+import java.io.ByteArrayInputStream;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -43,6 +42,8 @@ public class CayenneSQLTemplateProcessor implements 
SQLTemplateProcessor {
     ConcurrentLinkedHashMap<String, Node> templateCache = new 
ConcurrentLinkedHashMap
             .Builder<String, Node>().maximumWeightedCapacity(100).build();
 
+    TemplateParserPool parserPool = new TemplateParserPool();
+
     @Override
     public SQLStatement processTemplate(String template, Map<String, ?> 
parameters) {
         Context context = new Context();
@@ -65,16 +66,21 @@ public class CayenneSQLTemplateProcessor implements 
SQLTemplateProcessor {
     protected SQLStatement process(String template, Context context) {
         Node node = templateCache.get(template);
         if(node == null) {
-            SQLTemplateParser parser = new SQLTemplateParser(new 
BufferedReader(new StringReader(template)));
+            SQLTemplateParser parser = parserPool.get();
             try {
+                parser.ReInit(new ByteArrayInputStream(template.getBytes()));
                 node = parser.template();
             } catch (ParseException | TokenMgrError ex) {
                 throw new CayenneRuntimeException("Error parsing template '%s' 
: %s", template, ex.getMessage());
+            } finally {
+                parserPool.put(parser);
             }
+            // can ignore case when someone resolved this template 
concurrently, it has no side effects
             templateCache.put(template, node);
         }
 
-        String sql = node.evaluate(context);
-        return new SQLStatement(sql, context.getColumnDescriptors(), 
context.getParameterBindings());
+        node.evaluate(context);
+
+        return new SQLStatement(context.buildTemplate(), 
context.getColumnDescriptors(), context.getParameterBindings());
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java 
b/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java
index 61c37b2..bb528d2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/template/Context.java
@@ -34,7 +34,6 @@ import org.apache.cayenne.template.directive.BindObjectEqual;
 import org.apache.cayenne.template.directive.BindObjectNotEqual;
 import org.apache.cayenne.template.directive.Directive;
 import org.apache.cayenne.template.directive.Result;
-import org.apache.cayenne.velocity.SQLTemplateRenderingUtils;
 
 /**
  * @since 4.1
@@ -51,6 +50,8 @@ public class Context {
 
     List<ColumnDescriptor> columnDescriptors = new ArrayList<>();
 
+    StringBuilder builder = new StringBuilder();
+
     boolean positionalMode;
 
     int counter;
@@ -78,6 +79,19 @@ public class Context {
         return directives.get(name);
     }
 
+    public StringBuilder getBuilder() {
+        return builder;
+    }
+
+    public String buildTemplate() {
+        if(positionalMode) {
+            if(counter <= objects.size() - 2) {
+                throw new CayenneRuntimeException("Too many parameters to bind 
template: " + (objects.size() - 1));
+            }
+        }
+        return builder.toString();
+    }
+
     public Object getObject(String name) {
         Object object = objects.get(name);
         if(object != null) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/TemplateParserPool.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/TemplateParserPool.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/TemplateParserPool.java
new file mode 100644
index 0000000..eff632e
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/TemplateParserPool.java
@@ -0,0 +1,60 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.template;
+
+import java.io.ByteArrayInputStream;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import org.apache.cayenne.template.parser.SQLTemplateParser;
+
+/**
+ * @since 4.1
+ */
+class TemplateParserPool {
+
+    final static int INITIAL_POOL_SIZE = 4;
+    final static int MAX_POOL_SIZE = 20;
+
+    private ArrayBlockingQueue<SQLTemplateParser> parsers = new 
ArrayBlockingQueue<>(MAX_POOL_SIZE);
+
+    TemplateParserPool() {
+        for(int i=0; i<INITIAL_POOL_SIZE; i++) {
+            parsers.offer(createNewParser());
+        }
+    }
+
+    SQLTemplateParser get() {
+        SQLTemplateParser parser = parsers.poll();
+        if(parser == null) {
+            parser = createNewParser();
+        }
+        return parser;
+    }
+
+    void put(SQLTemplateParser parser) {
+        parser.ReInit(new ByteArrayInputStream("\n".getBytes()));
+        parsers.offer(parser);
+    }
+
+    SQLTemplateParser createNewParser() {
+        return new SQLTemplateParser(new 
ByteArrayInputStream("\n".getBytes()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java
index 8d2138f..23bcf43 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Bind.java
@@ -35,13 +35,13 @@ public class Bind implements Directive {
     public static final Bind INSTANCE = new Bind();
 
     @Override
-    public String apply(Context context, ASTExpression... expressions) {
-        if(expressions.length < 1) {
+    public void apply(Context context, ASTExpression... expressions) {
+        if (expressions.length < 1) {
             throw new IllegalArgumentException();
         }
 
         Object value = expressions[0].evaluateAsObject(context);
-        String jdbcTypeName = expressions.length < 2 ? null : 
expressions[1].evaluate(context);
+        String jdbcTypeName = expressions.length < 2 ? null : 
expressions[1].evaluateAsString(context);
 
         int jdbcType;
         if (jdbcTypeName != null) {
@@ -51,26 +51,23 @@ public class Bind implements Directive {
         } else {
             jdbcType = TypesMapping.getSqlTypeByName(TypesMapping.SQL_NULL);
         }
-        int scale = expressions.length < 3 ? -1 : 
(int)expressions[2].evaluateAsLong(context);
+        int scale = expressions.length < 3 ? -1 : (int) 
expressions[2].evaluateAsLong(context);
 
-        StringBuilder builder = new StringBuilder();
         if (value instanceof Collection) {
             Iterator<?> it = ((Collection) value).iterator();
             while (it.hasNext()) {
-                processBinding(context, builder, new 
ParameterBinding(it.next(), jdbcType, scale));
+                processBinding(context, new ParameterBinding(it.next(), 
jdbcType, scale));
                 if (it.hasNext()) {
-                    builder.append(',');
+                    context.getBuilder().append(',');
                 }
             }
         } else {
-            processBinding(context, builder, new ParameterBinding(value, 
jdbcType, scale));
+            processBinding(context, new ParameterBinding(value, jdbcType, 
scale));
         }
-
-        return builder.toString();
     }
 
-    protected void processBinding(Context context, StringBuilder builder, 
ParameterBinding binding) {
+    protected void processBinding(Context context, ParameterBinding binding) {
         context.addParameterBinding(binding);
-        builder.append('?');
+        context.getBuilder().append('?');
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindEqual.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindEqual.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindEqual.java
index 1153675..e343fe8 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindEqual.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindEqual.java
@@ -30,12 +30,12 @@ public class BindEqual extends Bind {
     public static final BindEqual INSTANCE = new BindEqual();
 
     @Override
-    protected void processBinding(Context context, StringBuilder builder, 
ParameterBinding binding) {
+    protected void processBinding(Context context, ParameterBinding binding) {
         if (binding.getValue() != null) {
             context.addParameterBinding(binding);
-            builder.append("= ?");
+            context.getBuilder().append("= ?");
         } else {
-            builder.append("IS NULL");
+            context.getBuilder().append("IS NULL");
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindNotEqual.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindNotEqual.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindNotEqual.java
index 09f70ed..e58200f 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindNotEqual.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindNotEqual.java
@@ -30,12 +30,12 @@ public class BindNotEqual extends Bind {
     public static final BindEqual INSTANCE = new BindEqual();
 
     @Override
-    protected void processBinding(Context context, StringBuilder builder, 
ParameterBinding binding) {
+    protected void processBinding(Context context, ParameterBinding binding) {
         if (binding.getValue() != null) {
             context.addParameterBinding(binding);
-            builder.append("<> ?");
+            context.getBuilder().append("<> ?");
         } else {
-            builder.append("IS NOT NULL");
+            context.getBuilder().append("IS NOT NULL");
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectEqual.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectEqual.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectEqual.java
index cea8b4f..bbf1605 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectEqual.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectEqual.java
@@ -24,13 +24,13 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.template.Context;
 import org.apache.cayenne.template.parser.ASTExpression;
-import org.apache.velocity.exception.ParseErrorException;
 
 /**
  * @since 4.1
@@ -40,7 +40,7 @@ public class BindObjectEqual implements Directive {
     public static final BindObjectEqual INSTANCE = new BindObjectEqual();
 
     @Override
-    public String apply(Context context, ASTExpression... expressions) {
+    public void apply(Context context, ASTExpression... expressions) {
 
         Object object = expressions[0].evaluateAsObject(context);
         Map<String, Object> idMap = toIdMap(object);
@@ -57,7 +57,7 @@ public class BindObjectEqual implements Directive {
         if (idMap == null) {
             // assume null object, and bind all null values
             if (sqlColumns == null || idColumns == null) {
-                throw new ParseErrorException("Invalid parameters. "
+                throw new CayenneRuntimeException("Invalid parameters. "
                         + "Either object has to be set or sqlColumns and 
idColumns or both.");
             }
 
@@ -72,37 +72,33 @@ public class BindObjectEqual implements Directive {
         String[] idColumnsArray = toArray(idColumns);
 
         if (sqlColumnsArray.length != idColumnsArray.length) {
-            throw new ParseErrorException(
+            throw new CayenneRuntimeException(
                     "SQL columns and ID columns arrays have different sizes.");
         }
 
-        StringBuilder builder = new StringBuilder();
-
         for (int i = 0; i < sqlColumnsArray.length; i++) {
             Object value = idMap.get(idColumnsArray[i]);
             int jdbcType = (value != null) ? 
TypesMapping.getSqlTypeByJava(value.getClass()) : Types.INTEGER;
 
-            renderColumn(sqlColumnsArray[i], i, builder);
-            render(context, builder, new ParameterBinding(value, jdbcType, 
-1));
+            renderColumn(context, sqlColumnsArray[i], i);
+            render(context, new ParameterBinding(value, jdbcType, -1));
         }
-
-        return builder.toString();
     }
 
-    protected void renderColumn(String columnName, int columnIndex, 
StringBuilder builder) {
+    protected void renderColumn(Context context, String columnName, int 
columnIndex) {
         if (columnIndex > 0) {
-            builder.append(" AND ");
+            context.getBuilder().append(" AND ");
         }
 
-        builder.append(columnName).append(' ');
+        context.getBuilder().append(columnName).append(' ');
     }
 
-    protected void render(Context context, StringBuilder builder, 
ParameterBinding binding) {
+    protected void render(Context context, ParameterBinding binding) {
         if (binding.getValue() != null) {
             context.addParameterBinding(binding);
-            builder.append("= ?");
+            context.getBuilder().append("= ?");
         } else {
-            builder.append("IS NULL");
+            context.getBuilder().append("IS NULL");
         }
     }
 
@@ -128,7 +124,7 @@ public class BindObjectEqual implements Directive {
     }
 
     @SuppressWarnings("unchecked")
-    protected Map<String, Object> toIdMap(Object object) throws 
ParseErrorException {
+    protected Map<String, Object> toIdMap(Object object) {
         if (object instanceof Persistent) {
             return ((Persistent) object).getObjectId().getIdSnapshot();
         } else if (object instanceof ObjectId) {
@@ -136,7 +132,7 @@ public class BindObjectEqual implements Directive {
         } else if(object instanceof Map) {
             return (Map<String, Object>) object;
         } else if (object != null) {
-            throw new ParseErrorException(
+            throw new CayenneRuntimeException(
                     "Invalid object parameter, expected Persistent or ObjectId 
or null: " + object);
         } else {
             return null;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectNotEqual.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectNotEqual.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectNotEqual.java
index 2e8879d..74c7e97 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectNotEqual.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/BindObjectNotEqual.java
@@ -30,21 +30,21 @@ public class BindObjectNotEqual extends BindObjectEqual {
     public static final BindObjectNotEqual INSTANCE = new BindObjectNotEqual();
 
     @Override
-    protected void renderColumn(String columnName, int columnIndex, 
StringBuilder builder) {
+    protected void renderColumn(Context context, String columnName, int 
columnIndex) {
         if (columnIndex > 0) {
-            builder.append(" OR ");
+            context.getBuilder().append(" OR ");
         }
 
-        builder.append(columnName).append(' ');
+        context.getBuilder().append(columnName).append(' ');
     }
 
     @Override
-    protected void render(Context context, StringBuilder builder, 
ParameterBinding binding) {
+    protected void render(Context context, ParameterBinding binding) {
         if (binding.getValue() != null) {
             context.addParameterBinding(binding);
-            builder.append("<> ?");
+            context.getBuilder().append("<> ?");
         } else {
-            builder.append("IS NOT NULL");
+            context.getBuilder().append("IS NOT NULL");
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Directive.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Directive.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Directive.java
index be2d6c9..cdc5da4 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Directive.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Directive.java
@@ -27,6 +27,6 @@ import org.apache.cayenne.template.parser.ASTExpression;
  */
 public interface Directive {
 
-    String apply(Context context, ASTExpression... expressions);
+    void apply(Context context, ASTExpression... expressions);
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Result.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Result.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Result.java
index dfcdd6f..967bb2c 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Result.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/directive/Result.java
@@ -73,26 +73,26 @@ public class Result implements Directive {
     }
 
     @Override
-    public String apply(Context context, ASTExpression... expressions) {
+    public void apply(Context context, ASTExpression... expressions) {
 
         ColumnDescriptor columnDescriptor = new ColumnDescriptor();
 
-        String column = expressions[0].evaluate(context);
+        String column = expressions[0].evaluateAsString(context);
         columnDescriptor.setName(column);
 
         if (expressions.length > 1) {
-            String type = expressions[1].evaluate(context);
+            String type = expressions[1].evaluateAsString(context);
             columnDescriptor.setJavaClass(guessType(type));
         }
 
         String alias = null;
         if (expressions.length > 2) {
-            alias = expressions[2].evaluate(context);
+            alias = expressions[2].evaluateAsString(context);
         }
 
         String dataRowKey = null;
         if (expressions.length > 3) {
-            dataRowKey = expressions[3].evaluate(context);
+            dataRowKey = expressions[3].evaluateAsString(context);
         }
 
         // determine what we want to name this column in a resulting DataRow...
@@ -106,12 +106,10 @@ public class Result implements Directive {
 
         context.addColumnDescriptor(columnDescriptor);
 
-        String result = column;
+        context.getBuilder().append(column);
         if (!Util.isEmptyString(alias) && !alias.equals(column)) {
-            result += " AS " + alias;
+            context.getBuilder().append(" AS ").append(alias);
         }
-
-        return result;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTArray.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTArray.java 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTArray.java
index 3aed8ba..dbb9299 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTArray.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTArray.java
@@ -30,7 +30,12 @@ public class ASTArray extends ASTExpression {
     }
 
     @Override
-    public String evaluate(Context context) {
+    public void evaluate(Context context) {
+        context.getBuilder().append(evaluateAsString(context));
+    }
+
+    @Override
+    public String evaluateAsString(Context context) {
         return Arrays.toString(evaluateAsArray(context));
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTBlock.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTBlock.java 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTBlock.java
index 743756f..0f50dff 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTBlock.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTBlock.java
@@ -34,11 +34,12 @@ public class ASTBlock extends SimpleNode {
     }
 
     @Override
-    public String evaluate(Context context) {
-        StringBuilder builder = new StringBuilder();
+    public void evaluate(Context context) {
+        if(children == null) {
+            return;
+        }
         for(Node node : children) {
-            builder.append(node.evaluate(context));
+            node.evaluate(context);
         }
-        return builder.toString();
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
index 3522123..cd3b656 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTDirective.java
@@ -32,10 +32,10 @@ public class ASTDirective extends IdentifierNode {
     }
 
     @Override
-    public String evaluate(Context context) {
+    public void evaluate(Context context) {
         Directive directive = context.getDirective(getIdentifier());
         if(directive == null) {
-            return "";
+            return;
         }
 
         ASTExpression[] expressions = new ASTExpression[children.length];
@@ -43,6 +43,6 @@ public class ASTDirective extends IdentifierNode {
             expressions[i] = (ASTExpression)children[i];
         }
 
-        return directive.apply(context, expressions);
+        directive.apply(context, expressions);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
index 7ff1858..a789156 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTExpression.java
@@ -35,8 +35,17 @@ public class ASTExpression extends SimpleNode implements 
ExpressionNode {
     }
 
     @Override
-    public String evaluate(Context context) {
-        return jjtGetChild(0).evaluate(context);
+    public void evaluate(Context context) {
+        jjtGetChild(0).evaluate(context);
+    }
+
+    @Override
+    public String evaluateAsString(Context context) {
+        Object object = evaluateAsObject(context);
+        if(object != null) {
+            return object.toString();
+        }
+        return "";
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTIfElse.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTIfElse.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTIfElse.java
index d117777..56bec44 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTIfElse.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTIfElse.java
@@ -31,16 +31,15 @@ public class ASTIfElse extends SimpleNode {
     }
 
     @Override
-    public String evaluate(Context context) {
+    public void evaluate(Context context) {
         ASTExpression condition = (ASTExpression)jjtGetChild(0);
         if (condition.evaluateAsBoolean(context)) {
-            return jjtGetChild(1).evaluate(context);
+            jjtGetChild(1).evaluate(context);
         } else {
             // else is optional
             if(jjtGetNumChildren() > 2) {
-                return jjtGetChild(2).evaluate(context);
+                jjtGetChild(2).evaluate(context);
             }
-            return "";
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
index 51e7358..c4b96a0 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTMethod.java
@@ -30,20 +30,14 @@ import org.apache.cayenne.template.Context;
  */
 public class ASTMethod extends IdentifierNode {
 
-    protected Object parentObject;
-
     public ASTMethod(int id) {
         super(id);
     }
 
-    protected void setParentObject(Object parentObject) {
-        this.parentObject = Objects.requireNonNull(parentObject);
-    }
-
     /**
      * Evaluate method call to an Object
      */
-    public Object evaluateAsObject(Context context) {
+    public Object evaluateAsObject(Context context, Object parentObject) {
         if(parentObject == null) {
             throw new IllegalStateException("To evaluate method node parent 
object should be set.");
         }
@@ -65,7 +59,7 @@ public class ASTMethod extends IdentifierNode {
                     for(Class<?> parameterType : m.getParameterTypes()) {
                         ASTExpression child = (ASTExpression)jjtGetChild(i);
                         if(parameterType.isAssignableFrom(String.class)) {
-                            arguments[i] = child.evaluate(context);
+                            arguments[i] = child.evaluateAsString(context);
                         } else 
if(parameterType.isAssignableFrom(Double.class)) {
                             arguments[i] = child.evaluateAsDouble(context);
                         } else if(parameterType.isAssignableFrom(Long.class)) {
@@ -94,9 +88,8 @@ public class ASTMethod extends IdentifierNode {
     }
 
     @Override
-    public String evaluate(Context context) {
-        Object object = evaluateAsObject(context);
-        return object == null ? "" : object.toString();
+    public void evaluate(Context context) {
+        throw new UnsupportedOperationException("Unable evaluate method 
directly, must be solved via ASTVariable");
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
index f45f1f8..3d2f60b 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ASTVariable.java
@@ -33,6 +33,12 @@ public class ASTVariable extends IdentifierNode implements 
ExpressionNode {
     }
 
     @Override
+    public String evaluateAsString(Context context) {
+        Object object = evaluateAsObject(context);
+        return object == null ? "" : object.toString();
+    }
+
+    @Override
     public Object evaluateAsObject(Context context) {
         Object object = context.getObject(getIdentifier());
         if(object == null) {
@@ -40,8 +46,7 @@ public class ASTVariable extends IdentifierNode implements 
ExpressionNode {
         }
         for(int i=0; i<jjtGetNumChildren(); i++) {
             ASTMethod method = (ASTMethod)jjtGetChild(i);
-            method.setParentObject(object);
-            object = method.evaluateAsObject(context);
+            object = method.evaluateAsObject(context, object);
             if(object == null) {
                 return null;
             }
@@ -50,9 +55,8 @@ public class ASTVariable extends IdentifierNode implements 
ExpressionNode {
     }
 
     @Override
-    public String evaluate(Context context) {
-        Object object = evaluateAsObject(context);
-        return object == null ? "" : object.toString();
+    public void evaluate(Context context) {
+        context.getBuilder().append(evaluateAsString(context));
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ExpressionNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ExpressionNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ExpressionNode.java
index 735badf..e9b1bd5 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ExpressionNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ExpressionNode.java
@@ -26,6 +26,8 @@ import org.apache.cayenne.template.Context;
  */
 public interface ExpressionNode {
 
+    String evaluateAsString(Context context);
+
     Object evaluateAsObject(Context context);
 
     long evaluateAsLong(Context context);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JJTSQLTemplateParserState.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JJTSQLTemplateParserState.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JJTSQLTemplateParserState.java
index 667462e..85f9ab7 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JJTSQLTemplateParserState.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JJTSQLTemplateParserState.java
@@ -19,17 +19,23 @@
 
 package org.apache.cayenne.template.parser;
 
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @since 4.1
+ */
 public class JJTSQLTemplateParserState {
-    private java.util.List<Node> nodes;
-    private java.util.List<Integer> marks;
+    private List<Node> nodes;
+    private List<Integer> marks;
 
     private int sp;        // number of nodes on stack
     private int mk;        // current mark
     private boolean node_created;
 
     public JJTSQLTemplateParserState() {
-        nodes = new java.util.ArrayList<>();
-        marks = new java.util.ArrayList<>();
+        nodes = new ArrayList<>();
+        marks = new ArrayList<>();
         sp = 0;
         mk = 0;
     }
@@ -82,7 +88,6 @@ public class JJTSQLTemplateParserState {
         return sp - mk;
     }
 
-
     public void clearNodeScope(Node n) {
         while (sp > mk) {
             popNode();
@@ -90,14 +95,11 @@ public class JJTSQLTemplateParserState {
         mk = marks.remove(marks.size() - 1);
     }
 
-
     public void openNodeScope(Node n) {
         marks.add(mk);
         mk = sp;
-        n.jjtOpen();
     }
 
-
     /* A definite node is constructed from a specified number of
        children.  That number of nodes are popped from the stack and
        made the children of the definite node.  Then the definite node
@@ -109,7 +111,6 @@ public class JJTSQLTemplateParserState {
             c.jjtSetParent(n);
             n.jjtAddChild(c, num);
         }
-        n.jjtClose();
         pushNode(n);
         node_created = true;
     }
@@ -129,7 +130,6 @@ public class JJTSQLTemplateParserState {
                 c.jjtSetParent(n);
                 n.jjtAddChild(c, a);
             }
-            n.jjtClose();
             pushNode(n);
             node_created = true;
         } else {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
index d9e6eb5..3373be4 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/JavaCharStream.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.template.parser;
 
+import java.io.IOException;
+
 /**
  * An implementation of interface CharStream, where the stream is assumed to
  * contain only ASCII characters (with java-like unicode escape processing).
@@ -27,6 +29,14 @@ package org.apache.cayenne.template.parser;
  */
 public class JavaCharStream {
 
+    private static final IOException END_OF_STREAM_EXCEPTION = new 
IOException() {
+
+        @Override
+        public synchronized Throwable fillInStackTrace() {
+            return this;
+        }
+    };
+
     /**
      * Whether parser is static.
      */
@@ -102,6 +112,7 @@ public class JavaCharStream {
     protected int nextCharInd = -1;
     protected int inBuf = 0;
     protected int tabSize = 8;
+    private boolean closed;
 
     protected void ExpandBuff(boolean wrapAround) {
         char[] newbuffer = new char[bufsize + 2048];
@@ -145,14 +156,17 @@ public class JavaCharStream {
 
     protected void FillBuff() throws java.io.IOException {
         int i;
-        if (maxNextCharInd == 4096)
+        if (maxNextCharInd == nextCharBuf.length)
             maxNextCharInd = nextCharInd = 0;
 
         try {
-            if ((i = inputStream.read(nextCharBuf, maxNextCharInd,
-                    4096 - maxNextCharInd)) == -1) {
+            // check for closed status to prevent the underlying Reader from
+            // throwing IOException that causes performance degradation
+            if (closed || (i = inputStream.read(nextCharBuf, maxNextCharInd,
+                    nextCharBuf.length - maxNextCharInd)) == -1) {
                 inputStream.close();
-                throw new java.io.IOException();
+                closed = true;
+                throw END_OF_STREAM_EXCEPTION;
             } else
                 maxNextCharInd += i;
         } catch (java.io.IOException e) {
@@ -412,6 +426,7 @@ public class JavaCharStream {
         prevCharIsLF = prevCharIsCR = false;
         tokenBegin = inBuf = maxNextCharInd = 0;
         nextCharInd = bufpos = -1;
+        closed = false;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/Node.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/Node.java 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/Node.java
index 15d5b1a..eb40295 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/Node.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/Node.java
@@ -30,18 +30,6 @@ import org.apache.cayenne.template.Context;
 public interface Node {
 
     /**
-     * This method is called after the node has been made the current
-     * node.  It indicates that child nodes can now be added to it.
-     */
-    void jjtOpen();
-
-    /**
-     * This method is called after all the child nodes have been
-     * added.
-     */
-    void jjtClose();
-
-    /**
      * This pair of methods are used to inform the node of its
      * parent.
      */
@@ -66,5 +54,5 @@ public interface Node {
      */
     int jjtGetNumChildren();
 
-    String evaluate(Context context);
+    void evaluate(Context context);
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
index a175ae9..46ebe43 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SQLTemplateParser.java
@@ -114,21 +114,18 @@ public class SQLTemplateParser/*@bgen(jjtree)*/implements 
SQLTemplateParserTreeC
       switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
       case TEXT:
         t = jj_consume_token(TEXT);
-                 jjtree.closeNodeScope(jjtn000, true);
-                 jjtc000 = false;
-        jjtn000.setValue(t.image);
         break;
       case TEXT_OTHER:
         t = jj_consume_token(TEXT_OTHER);
-                       jjtree.closeNodeScope(jjtn000, true);
-                       jjtc000 = false;
-        jjtn000.setValue(t.image);
         break;
       default:
         jj_la1[2] = jj_gen;
         jj_consume_token(-1);
         throw new ParseException();
       }
+        jjtree.closeNodeScope(jjtn000, true);
+        jjtc000 = false;
+        jjtn000.setValue(t.image);
     } finally {
       if (jjtc000) {
         jjtree.closeNodeScope(jjtn000, true);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ScalarNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ScalarNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ScalarNode.java
index ba2a39a..106dd06 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ScalarNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/ScalarNode.java
@@ -41,10 +41,14 @@ public class ScalarNode<V> extends SimpleNode implements 
ExpressionNode {
     }
 
     @Override
-    public String evaluate(Context context) {
-        if(value == null) {
-            return "";
+    public void evaluate(Context context) {
+        if(value != null) {
+            context.getBuilder().append(value.toString());
         }
+    }
+
+    @Override
+    public String evaluateAsString(Context context) {
         return value.toString();
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SimpleNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SimpleNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SimpleNode.java
index df4fc8a..12116fb 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SimpleNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/template/parser/SimpleNode.java
@@ -32,20 +32,17 @@ public abstract class SimpleNode implements Node {
         id = i;
     }
 
-    public void jjtOpen() {
-    }
-
-    public void jjtClose() {
-    }
-
+    @Override
     public void jjtSetParent(Node n) {
         parent = n;
     }
 
+    @Override
     public Node jjtGetParent() {
         return parent;
     }
 
+    @Override
     public void jjtAddChild(Node n, int i) {
         if (children == null) {
             children = new Node[i + 1];
@@ -57,14 +54,17 @@ public abstract class SimpleNode implements Node {
         children[i] = n;
     }
 
+    @Override
     public Node jjtGetChild(int i) {
         return children[i];
     }
 
+    @Override
     public int jjtGetNumChildren() {
         return (children == null) ? 0 : children.length;
     }
 
+    @Override
     public String toString() {
         return SQLTemplateParserTreeConstants.jjtNodeName[id];
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/main/jjtree/org/apache/cayenne/template/parser/SQLTemplateParser.jjt
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/jjtree/org/apache/cayenne/template/parser/SQLTemplateParser.jjt
 
b/cayenne-server/src/main/jjtree/org/apache/cayenne/template/parser/SQLTemplateParser.jjt
index ceeb0f4..b4114bc 100644
--- 
a/cayenne-server/src/main/jjtree/org/apache/cayenne/template/parser/SQLTemplateParser.jjt
+++ 
b/cayenne-server/src/main/jjtree/org/apache/cayenne/template/parser/SQLTemplateParser.jjt
@@ -99,7 +99,8 @@ Node template() : {}
 */
 void block() #Block : {}
 {
-    (   ifElse()
+    (
+        ifElse()
     |   directive()
     |   variable()
     |   text()
@@ -113,11 +114,10 @@ void text() #Text : {
     Token t;
 }
 {
-    t = <TEXT> {
-        jjtThis.setValue(t.image);
-    }
-    |
-    t = <TEXT_OTHER> {
+    (
+        t = <TEXT>
+    |   t = <TEXT_OTHER>
+    ) {
         jjtThis.setValue(t.image);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/test/java/org/apache/cayenne/query/SQLTemplateIT.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/query/SQLTemplateIT.java 
b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLTemplateIT.java
index be882b9..902ff04 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/SQLTemplateIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/SQLTemplateIT.java
@@ -148,15 +148,11 @@ public class SQLTemplateIT extends ServerCase {
                SQLTemplate q1 = new SQLTemplate(Painting.class, sql);
                q1.setParamsArray(11, "The Fiddler", 2345, 333);
 
-               context.performNonSelectingQuery(q1);
-               // TODO: new template render doesn't throw expetion in this case
-               /*
                try {
                        context.performNonSelectingQuery(q1);
                        fail("Exception not thrown on parameter length 
mismatch");
                } catch (CayenneRuntimeException e) {
                        // expected
                }
-               */
        }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/test/java/org/apache/cayenne/template/TemplateParserPoolTest.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/template/TemplateParserPoolTest.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/template/TemplateParserPoolTest.java
new file mode 100644
index 0000000..dce5efd
--- /dev/null
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/template/TemplateParserPoolTest.java
@@ -0,0 +1,72 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.template;
+
+import java.io.ByteArrayInputStream;
+
+import org.apache.cayenne.template.parser.SQLTemplateParser;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.1
+ */
+public class TemplateParserPoolTest {
+
+    TemplateParserPool parserPool;
+
+    @Before
+    public void createPool() {
+        parserPool = new TemplateParserPool();
+    }
+
+    @Test
+    public void get() throws Exception {
+        for(int i=0; i<TemplateParserPool.MAX_POOL_SIZE + 10; i++) {
+            SQLTemplateParser parser = parserPool.get();
+            assertNotNull(parser);
+        }
+    }
+
+    @Test
+    public void put() throws Exception {
+        SQLTemplateParser parser = new SQLTemplateParser(new 
ByteArrayInputStream("".getBytes()));
+
+        parserPool.put(parser);
+
+        for(int i=0; i<TemplateParserPool.INITIAL_POOL_SIZE; i++) {
+            SQLTemplateParser parser1 = parserPool.get();
+            assertNotNull(parser1);
+            assertNotSame(parser, parser1);
+        }
+
+        SQLTemplateParser parser1 = parserPool.get();
+        assertSame(parser, parser1);
+    }
+
+    @Test
+    public void createNewParser() throws Exception {
+        SQLTemplateParser parser = parserPool.createNewParser();
+        assertNotNull(parser);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/54feb976/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java
index 9d74f56..6d26b20 100644
--- 
a/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/template/parser/SQLTemplateParserTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.template.parser;
 
+import java.io.ByteArrayInputStream;
 import java.io.StringReader;
 
 import org.apache.cayenne.template.Context;
@@ -158,6 +159,8 @@ public class SQLTemplateParserTest {
         String sql = parseString(template, context);
         assertEquals("\"val\"", sql);
 
+        context = new Context();
+        context.addParameter("a", "val");
         template = "'$a'";
         sql = parseString(template, context);
         assertEquals("'val'", sql);
@@ -172,10 +175,9 @@ public class SQLTemplateParserTest {
         assertEquals("val,val", sql);
     }
 
-    private String parseString(String template, Context context) throws 
ParseException {
-        SQLTemplateParser parser = new SQLTemplateParser(new 
StringReader(template));
-        Node block = parser.template();
-        return block.evaluate(context);
+    private String parseString(String tpl, Context context) throws 
ParseException {
+        new SQLTemplateParser(new 
ByteArrayInputStream(tpl.getBytes())).template().evaluate(context);
+        return context.buildTemplate();
     }
 
 }
\ No newline at end of file

Reply via email to