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