CAY-1966 SQLTemplate/SQLSelect positional parameter binding * new org.apache.cayenne.velocity package * generics fixes in unit tests
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/c8709542 Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/c8709542 Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/c8709542 Branch: refs/heads/master Commit: c870954210bbe2838b49acd13ea3f4455c4e44fb Parents: d3c5b72 Author: aadamchik <[email protected]> Authored: Sun Nov 2 15:49:38 2014 +0300 Committer: aadamchik <[email protected]> Committed: Sun Nov 2 16:10:21 2014 +0300 ---------------------------------------------------------------------- .../cayenne/access/jdbc/BindDirective.java | 175 ------------ .../cayenne/access/jdbc/BindEqualDirective.java | 56 ---- .../access/jdbc/BindNotEqualDirective.java | 55 ---- .../access/jdbc/BindObjectEqualDirective.java | 163 ----------- .../jdbc/BindObjectNotEqualDirective.java | 69 ----- .../cayenne/access/jdbc/ChainDirective.java | 112 -------- .../cayenne/access/jdbc/ChunkDirective.java | 75 ------ .../cayenne/access/jdbc/ResultDirective.java | 205 -------------- .../cayenne/access/jdbc/SQLTemplateAction.java | 1 + .../access/jdbc/SQLTemplateProcessor.java | 168 ------------ .../access/jdbc/SQLTemplateRenderingUtils.java | 37 --- .../access/jdbc/SQLTemplateResourceManager.java | 106 -------- .../apache/cayenne/velocity/BindDirective.java | 176 ++++++++++++ .../cayenne/velocity/BindEqualDirective.java | 57 ++++ .../cayenne/velocity/BindNotEqualDirective.java | 56 ++++ .../velocity/BindObjectEqualDirective.java | 164 ++++++++++++ .../velocity/BindObjectNotEqualDirective.java | 70 +++++ .../apache/cayenne/velocity/ChainDirective.java | 112 ++++++++ .../apache/cayenne/velocity/ChunkDirective.java | 75 ++++++ .../cayenne/velocity/ResultDirective.java | 206 ++++++++++++++ .../cayenne/velocity/SQLTemplateProcessor.java | 155 +++++++++++ .../velocity/SQLTemplateRenderingUtils.java | 37 +++ .../velocity/SQLTemplateResourceManager.java | 106 ++++++++ .../cayenne/access/jdbc/BindDirectiveIT.java | 267 ------------------ .../access/jdbc/MockupRuntimeServices.java | 42 --- .../cayenne/access/jdbc/ResultDirectiveIT.java | 187 ------------- .../jdbc/SQLTemplateProcessorChainTest.java | 219 --------------- .../jdbc/SQLTemplateProcessorSelectTest.java | 109 -------- .../access/jdbc/SQLTemplateProcessorTest.java | 260 ------------------ .../jdbc/SQLTemplateResourceManagerTest.java | 65 ----- .../cayenne/velocity/BindDirectiveIT.java | 268 +++++++++++++++++++ .../cayenne/velocity/ResultDirectiveIT.java | 188 +++++++++++++ .../velocity/SQLTemplateProcessorChainTest.java | 221 +++++++++++++++ .../SQLTemplateProcessorSelectTest.java | 112 ++++++++ .../velocity/SQLTemplateProcessorTest.java | 235 ++++++++++++++++ .../SQLTemplateResourceManagerTest.java | 77 ++++++ 36 files changed, 2316 insertions(+), 2370 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindDirective.java deleted file mode 100644 index 59245a5..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindDirective.java +++ /dev/null @@ -1,175 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.IOException; -import java.io.Writer; -import java.util.Collection; -import java.util.Iterator; - -import org.apache.cayenne.dba.TypesMapping; -import org.apache.cayenne.util.ConversionUtil; -import org.apache.velocity.context.InternalContextAdapter; -import org.apache.velocity.exception.MethodInvocationException; -import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.runtime.directive.Directive; -import org.apache.velocity.runtime.parser.node.Node; - -/** - * A custom Velocity directive to create a PreparedStatement parameter text. There are the - * following possible invocation formats inside the template: - * - * <pre> - * #bind(value) - e.g. #bind($xyz) - * #bind(value jdbc_type_name) - e.g. #bind($xyz 'VARCHAR'). This is the most common and useful form. - * #bind(value jdbc_type_name, scale) - e.g. #bind($xyz 'VARCHAR' 2) - * </pre> - * <p> - * Other examples: - * </p> - * <p> - * <strong>Binding literal parameter value:</strong> - * </p> - * <p> - * <code>"WHERE SOME_COLUMN > #bind($xyz)"</code> produces - * <code>"WHERE SOME_COLUMN > ?"</code> and also places the value of the "xyz" parameter - * in the context "bindings" collection. - * </p> - * <p> - * <strong>Binding ID column of a DataObject value:</strong> - * </p> - * <p> - * <code>"WHERE ID_COL1 = #bind($helper.cayenneExp($xyz, 'db:ID_COL2')) - * AND ID_COL2 = #bind($helper.cayenneExp($xyz, 'db:ID_COL2'))"</code> produces <code>"WHERE ID_COL1 = ? AND ID_COL2 = ?"</code> and also places the - * values of id columns of the DataObject parameter "xyz" in the context "bindings" - * collection. - * </p> - * - * @since 1.1 - */ -public class BindDirective extends Directive { - - @Override - public String getName() { - return "bind"; - } - - @Override - public int getType() { - return LINE; - } - - /** - * Extracts the value of the object property to render and passes control to - * {@link #render(InternalContextAdapter, Writer, ParameterBinding)} to do the actual - * rendering. - */ - @Override - public boolean render(InternalContextAdapter context, Writer writer, Node node) - throws IOException, ResourceNotFoundException, ParseErrorException, - MethodInvocationException { - - Object value = getChild(context, node, 0); - Object type = getChild(context, node, 1); - int scale = ConversionUtil.toInt(getChild(context, node, 2), -1); - String typeString = type != null ? type.toString() : null; - - if (value instanceof Collection) { - Iterator<?> it = ((Collection) value).iterator(); - while (it.hasNext()) { - render(context, writer, node, it.next(), typeString, scale); - - if (it.hasNext()) { - writer.write(','); - } - } - } - else { - render(context, writer, node, value, typeString, scale); - } - - return true; - } - - /** - * @since 3.0 - */ - protected void render( - InternalContextAdapter context, - Writer writer, - Node node, - Object value, - String typeString, - int scale) throws IOException, ParseErrorException { - - int jdbcType = TypesMapping.NOT_DEFINED; - if (typeString != null) { - jdbcType = TypesMapping.getSqlTypeByName(typeString); - } - else if (value != null) { - jdbcType = TypesMapping.getSqlTypeByJava(value.getClass()); - } else { - // value is null, set JDBC type to NULL - jdbcType = TypesMapping.getSqlTypeByName(TypesMapping.SQL_NULL); - } - - if (jdbcType == TypesMapping.NOT_DEFINED) { - throw new ParseErrorException("Can't determine JDBC type of binding (" - + value - + ", " - + typeString - + ") at line " - + node.getLine() - + ", column " - + node.getColumn()); - } - - render(context, writer, new ParameterBinding(value, jdbcType, scale)); - } - - protected void render( - InternalContextAdapter context, - Writer writer, - ParameterBinding binding) throws IOException { - - bind(context, binding); - writer.write('?'); - } - - protected Object getChild(InternalContextAdapter context, Node node, int i) - throws MethodInvocationException { - return (i >= 0 && i < node.jjtGetNumChildren()) ? node.jjtGetChild(i).value( - context) : null; - } - - /** - * Adds value to the list of bindings in the context. - */ - protected void bind(InternalContextAdapter context, ParameterBinding binding) { - - Collection bindings = (Collection) context.getInternalUserContext().get( - SQLTemplateProcessor.BINDINGS_LIST_KEY); - - if (bindings != null) { - bindings.add(binding); - } - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindEqualDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindEqualDirective.java deleted file mode 100644 index 773daa6..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindEqualDirective.java +++ /dev/null @@ -1,56 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.IOException; -import java.io.Writer; - -import org.apache.velocity.context.InternalContextAdapter; - -/** - * A custom Velocity directive to create a PreparedStatement parameter text - * for "= ?". If null value is encountered, generated text will look like "IS NULL". - * Usage in Velocity template is "WHERE SOME_COLUMN #bindEqual($xyz)". - * - * @since 1.1 - */ -public class BindEqualDirective extends BindDirective { - - @Override - public String getName() { - return "bindEqual"; - } - - @Override - protected void render( - InternalContextAdapter context, - Writer writer, - ParameterBinding binding) - throws IOException { - - if (binding.getValue() != null) { - bind(context, binding); - writer.write("= ?"); - } - else { - writer.write("IS NULL"); - } - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindNotEqualDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindNotEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindNotEqualDirective.java deleted file mode 100644 index ee54602..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindNotEqualDirective.java +++ /dev/null @@ -1,55 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.IOException; -import java.io.Writer; - -import org.apache.velocity.context.InternalContextAdapter; - -/** - * A custom Velocity directive to create a PreparedStatement parameter text for "<>?". - * If null value is encountered, generated text will look like "IS NOT NULL". Usage in - * Velocity template is "WHERE SOME_COLUMN #bindNotEqual($xyz)". - * - * @since 1.1 - */ -public class BindNotEqualDirective extends BindDirective { - - @Override - public String getName() { - return "bindNotEqual"; - } - - @Override - protected void render( - InternalContextAdapter context, - Writer writer, - ParameterBinding binding) throws IOException { - - if (binding.getValue() != null) { - bind(context, binding); - writer.write("<> ?"); - } - else { - writer.write("IS NOT NULL"); - } - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectEqualDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectEqualDirective.java deleted file mode 100644 index f35906c..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectEqualDirective.java +++ /dev/null @@ -1,163 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.IOException; -import java.io.Writer; -import java.sql.Types; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -import org.apache.cayenne.ObjectId; -import org.apache.cayenne.Persistent; -import org.apache.cayenne.dba.TypesMapping; -import org.apache.velocity.context.InternalContextAdapter; -import org.apache.velocity.exception.MethodInvocationException; -import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.runtime.parser.node.Node; - -/** - * A custom Velocity directive to create a set of SQL conditions to match an ObjectId of - * an object. Usage in Velocity template is "WHERE #bindObjectEqual($object)" or "WHERE - * #bindObjectEqual($object $columns $idValues)". - * - * @since 3.0 - */ -public class BindObjectEqualDirective extends BindDirective { - - @Override - public String getName() { - return "bindObjectEqual"; - } - - @Override - public boolean render(InternalContextAdapter context, Writer writer, Node node) - throws IOException, ResourceNotFoundException, ParseErrorException, - MethodInvocationException { - - Object object = getChild(context, node, 0); - Map idMap = toIdMap(object); - - Object sqlColumns = getChild(context, node, 1); - Object idColumns = getChild(context, node, 2); - - if (idMap == null) { - // assume null object, and bind all null values - - if (sqlColumns == null || idColumns == null) { - throw new ParseErrorException("Invalid parameters. " - + "Either object has to be set " - + "or sqlColumns and idColumns or both."); - } - - idMap = Collections.EMPTY_MAP; - } - else if (sqlColumns == null || idColumns == null) { - // infer SQL columns from ID columns - sqlColumns = idMap.keySet().toArray(); - idColumns = sqlColumns; - } - - Object[] sqlColumnsArray = toArray(sqlColumns); - Object[] idColumnsArray = toArray(idColumns); - - if (sqlColumnsArray.length != idColumnsArray.length) { - throw new ParseErrorException( - "SQL columns and ID columns arrays have different sizes."); - } - - 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(context, writer, sqlColumnsArray[i], i); - writer.write(' '); - render(context, writer, new ParameterBinding(value, jdbcType, -1)); - } - - return true; - } - - protected Object[] toArray(Object columns) { - if (columns instanceof Collection) { - return ((Collection) columns).toArray(); - } - else if (columns.getClass().isArray()) { - return (Object[]) columns; - } - else { - return new Object[] { - columns - }; - } - } - - protected Map toIdMap(Object object) throws ParseErrorException { - if (object instanceof Persistent) { - return ((Persistent) object).getObjectId().getIdSnapshot(); - } - else if (object instanceof ObjectId) { - return ((ObjectId) object).getIdSnapshot(); - } - else if(object instanceof Map) { - return (Map) object; - } - else if (object != null) { - throw new ParseErrorException( - "Invalid object parameter, expected Persistent or ObjectId or null: " - + object); - } - else { - return null; - } - } - - protected void renderColumn( - InternalContextAdapter context, - Writer writer, - Object columnName, - int columnIndex) throws IOException { - - if (columnIndex > 0) { - writer.write(" AND "); - } - - writer.write(columnName.toString()); - } - - @Override - protected void render( - InternalContextAdapter context, - Writer writer, - ParameterBinding binding) throws IOException { - - if (binding.getValue() != null) { - bind(context, binding); - writer.write("= ?"); - } - else { - writer.write("IS NULL"); - } - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectNotEqualDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectNotEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectNotEqualDirective.java deleted file mode 100644 index 0a51eed..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectNotEqualDirective.java +++ /dev/null @@ -1,69 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.IOException; -import java.io.Writer; - -import org.apache.velocity.context.InternalContextAdapter; - -/** - * A custom Velocity directive to create a set of SQL conditions to check unequality of an - * ObjectId of an object. Usage in Velocity template is "WHERE - * #bindObjectNotEqual($object)" or "WHERE #bindObjectNotEqual($object $columns - * $idValues)". - * - * @since 3.0 - */ -public class BindObjectNotEqualDirective extends BindObjectEqualDirective { - - @Override - public String getName() { - return "bindObjectNotEqual"; - } - - @Override - protected void renderColumn( - InternalContextAdapter context, - Writer writer, - Object columnName, - int columnIndex) throws IOException { - - if (columnIndex > 0) { - writer.write(" OR "); - } - - writer.write(columnName.toString()); - } - - @Override - protected void render( - InternalContextAdapter context, - Writer writer, - ParameterBinding binding) throws IOException { - - if (binding.getValue() != null) { - bind(context, binding); - writer.write("<> ?"); - } - else { - writer.write("IS NOT NULL"); - } - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChainDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChainDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChainDirective.java deleted file mode 100644 index 30a7816..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChainDirective.java +++ /dev/null @@ -1,112 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; - -import org.apache.velocity.context.InternalContextAdapter; -import org.apache.velocity.exception.MethodInvocationException; -import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.runtime.directive.Directive; -import org.apache.velocity.runtime.parser.node.ASTDirective; -import org.apache.velocity.runtime.parser.node.Node; - -/** - * A custom Velocity directive to conditionally join a number of {@link ChunkDirective chunks}. - * Usage of chain is the following: - * - * <pre> - * #chain(operator) - e.g. #chain(' AND ') - * #chain(operator prefix) - e.g. #chain(' AND ' 'WHERE ')</pre> - * - * <p><code>operator</code> (e.g. AND, OR, etc.) is used to join chunks that are included - * in a chain. <code>prefix</code> is inserted if a chain contains at least one chunk. - * </p> - * - * @since 1.1 - */ -public class ChainDirective extends Directive { - - @Override - public String getName() { - return "chain"; - } - - @Override - public int getType() { - return BLOCK; - } - - @Override - public boolean render(InternalContextAdapter context, Writer writer, Node node) - throws - IOException, - ResourceNotFoundException, - ParseErrorException, - MethodInvocationException { - - int size = node.jjtGetNumChildren(); - if (size == 0) { - return true; - } - - // BLOCK is the last child - Node block = node.jjtGetChild(node.jjtGetNumChildren() - 1); - String join = (size > 1) ? (String) node.jjtGetChild(0).value(context) : ""; - String prefix = (size > 2) ? (String) node.jjtGetChild(1).value(context) : ""; - - // if there is a conditional prefix, use a separate buffer ofr children - StringWriter childWriter = new StringWriter(30); - - int len = block.jjtGetNumChildren(); - int includedChunks = 0; - for (int i = 0; i < len; i++) { - Node child = block.jjtGetChild(i); - - // if this is a "chunk", evaluate its expression and prepend join if included... - if (child instanceof ASTDirective - && "chunk".equals(((ASTDirective) child).getDirectiveName())) { - - if (child.jjtGetNumChildren() < 2 - || child.jjtGetChild(0).value(context) != null) { - - if (includedChunks > 0) { - childWriter.write(join); - } - - includedChunks++; - } - } - - child.render(context, childWriter); - } - - if (includedChunks > 0) { - childWriter.flush(); - writer.write(prefix); - writer.write(childWriter.toString()); - } - - return true; - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChunkDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChunkDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChunkDirective.java deleted file mode 100644 index fb7c870..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChunkDirective.java +++ /dev/null @@ -1,75 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.IOException; -import java.io.Writer; - -import org.apache.velocity.context.InternalContextAdapter; -import org.apache.velocity.exception.MethodInvocationException; -import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.runtime.directive.Directive; -import org.apache.velocity.runtime.parser.node.Node; - -/** - * A custom Velocity directive to describe a conditional chunk of a {@link ChainDirective chain}. - * Usage of chunk is the following: - * - * <pre> - * #chunk()...#end - e.g. #chunk()A = 5#end - * #chunk($paramKey)...#end - e.g. #chunk($a)A = $a#end - * </pre> - * <p> - * If context contains paramKey and it's value isn't null, chunk is included in the - * chain, and if it is not the first chunk, it is prefixed with chain join (OR/AND). - * If context doesn't contain paramKey or it's value is null, chunk is skipped. - * @since 1.1 - */ -public class ChunkDirective extends Directive { - - @Override - public String getName() { - return "chunk"; - } - - @Override - public int getType() { - return BLOCK; - } - - @Override - public boolean render(InternalContextAdapter context, Writer writer, Node node) - throws IOException, ResourceNotFoundException, ParseErrorException, - MethodInvocationException { - - // first child is an expression, second is BLOCK - if (node.jjtGetNumChildren() > 1 && node.jjtGetChild(0).value(context) == null) { - // skip this chunk - return false; - } - - // BLOCK is the last child - Node block = node.jjtGetChild(node.jjtGetNumChildren() - 1); - block.render(context, writer); - return true; - } - -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ResultDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ResultDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ResultDirective.java deleted file mode 100644 index c24804a..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ResultDirective.java +++ /dev/null @@ -1,205 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.IOException; -import java.io.Writer; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.apache.velocity.context.InternalContextAdapter; -import org.apache.velocity.exception.MethodInvocationException; -import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.runtime.directive.Directive; -import org.apache.velocity.runtime.parser.node.Node; -import org.apache.cayenne.util.Util; - -/** - * A custom Velocity directive to describe a ResultSet column. There are the following - * possible invocation formats inside the template: - * - * <pre> - * #result(column_name) - e.g. #result('ARTIST_ID') - * #result(column_name java_type) - e.g. #result('ARTIST_ID' 'String') - * #result(column_name java_type column_alias) - e.g. #result('ARTIST_ID' 'String' 'ID') - * #result(column_name java_type column_alias data_row_key) - e.g. #result('ARTIST_ID' 'String' 'ID' 'toArtist.ID') - * </pre> - * - * <p> - * 'data_row_key' is needed if SQL 'column_alias' is not appropriate as a DataRow key on - * the Cayenne side. One common case when this happens is when a DataRow retrieved from a - * query is mapped using joint prefetch keys. In this case DataRow must use DB_PATH - * expressions for joint column keys, and their format is incompatible with most databases - * alias format. - * </p> - * <p> - * Most common Java types used in JDBC can be specified without a package. This includes - * all numeric types, primitives, String, SQL dates, BigDecimal and BigInteger. - * </p> - * - * @since 1.1 - */ -public class ResultDirective extends Directive { - - private static final Map<String, String> typesGuess; - - static { - // init default types - typesGuess = new HashMap<String, String>(); - - // primitives - typesGuess.put("long", Long.class.getName()); - typesGuess.put("double", Double.class.getName()); - typesGuess.put("byte", Byte.class.getName()); - typesGuess.put("boolean", Boolean.class.getName()); - typesGuess.put("float", Float.class.getName()); - typesGuess.put("short", Short.class.getName()); - typesGuess.put("int", Integer.class.getName()); - - // numeric - typesGuess.put("Long", Long.class.getName()); - typesGuess.put("Double", Double.class.getName()); - typesGuess.put("Byte", Byte.class.getName()); - typesGuess.put("Boolean", Boolean.class.getName()); - typesGuess.put("Float", Float.class.getName()); - typesGuess.put("Short", Short.class.getName()); - typesGuess.put("Integer", Integer.class.getName()); - - // other - typesGuess.put("String", String.class.getName()); - typesGuess.put("Date", Date.class.getName()); - typesGuess.put("Time", Time.class.getName()); - typesGuess.put("Timestamp", Timestamp.class.getName()); - typesGuess.put("BigDecimal", BigDecimal.class.getName()); - typesGuess.put("BigInteger", BigInteger.class.getName()); - } - - @Override - public String getName() { - return "result"; - } - - @Override - public int getType() { - return LINE; - } - - @Override - public boolean render(InternalContextAdapter context, Writer writer, Node node) - throws IOException, ResourceNotFoundException, ParseErrorException, - MethodInvocationException { - - String column = getChildAsString(context, node, 0); - if (column == null) { - throw new ParseErrorException("Column name expected at line " - + node.getLine() - + ", column " - + node.getColumn()); - } - - String alias = getChildAsString(context, node, 2); - String dataRowKey = getChildAsString(context, node, 3); - - // determine what we want to name this column in a resulting DataRow... - String label = (!Util.isEmptyString(dataRowKey)) ? dataRowKey : (!Util - .isEmptyString(alias)) ? alias : null; - - ColumnDescriptor columnDescriptor = new ColumnDescriptor(); - columnDescriptor.setName(column); - columnDescriptor.setDataRowKey(label); - - String type = getChildAsString(context, node, 1); - if (type != null) { - columnDescriptor.setJavaClass(guessType(type)); - } - - // TODO: andrus 6/27/2007 - this is an unofficial jdbcType parameter that is added - // temporarily pending CAY-813 implementation for the sake of EJBQL query... - Object jdbcType = getChild(context, node, 4); - if (jdbcType instanceof Number) { - columnDescriptor.setJdbcType(((Number) jdbcType).intValue()); - } - - writer.write(column); - - // append column alias if needed. - - // Note that if table aliases are used, this logic will result in SQL like - // "t0.ARTIST_NAME AS ARTIST_NAME". Doing extra regex matching to handle this - // won't probably buy us much. - if (!Util.isEmptyString(alias) && !alias.equals(column)) { - writer.write(" AS "); - writer.write(alias); - } - - bindResult(context, columnDescriptor); - return true; - } - - protected Object getChild(InternalContextAdapter context, Node node, int i) - throws MethodInvocationException { - return (i >= 0 && i < node.jjtGetNumChildren()) ? node.jjtGetChild(i).value( - context) : null; - } - - /** - * Returns a directive argument at a given index converted to String. - * - * @since 1.2 - */ - protected String getChildAsString(InternalContextAdapter context, Node node, int i) - throws MethodInvocationException { - Object value = getChild(context, node, i); - return (value != null) ? value.toString() : null; - } - - /** - * Converts "short" type notation to the fully qualified class name. Right now - * supports all major standard SQL types, including primitives. All other types are - * expected to be fully qualified, and are not converted. - */ - protected String guessType(String type) { - String guessed = typesGuess.get(type); - return guessed != null ? guessed : type; - } - - /** - * Adds value to the list of result columns in the context. - */ - protected void bindResult( - InternalContextAdapter context, - ColumnDescriptor columnDescriptor) { - - Collection<Object> resultColumns = (Collection<Object>) context - .getInternalUserContext() - .get(SQLTemplateProcessor.RESULT_COLUMNS_LIST_KEY); - - if (resultColumns != null) { - resultColumns.add(columnDescriptor); - } - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java index e86f04b..50e8691 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java @@ -49,6 +49,7 @@ import org.apache.cayenne.query.QueryMetadata; import org.apache.cayenne.query.SQLAction; import org.apache.cayenne.query.SQLTemplate; import org.apache.cayenne.util.Util; +import org.apache.cayenne.velocity.SQLTemplateProcessor; import org.apache.commons.collections.IteratorUtils; /** http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessor.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessor.java deleted file mode 100644 index d5f487e..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessor.java +++ /dev/null @@ -1,168 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.cayenne.CayenneRuntimeException; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.context.InternalContextAdapterImpl; -import org.apache.velocity.runtime.RuntimeConstants; -import org.apache.velocity.runtime.RuntimeInstance; -import org.apache.velocity.runtime.log.NullLogChute; -import org.apache.velocity.runtime.parser.ParseException; -import org.apache.velocity.runtime.parser.node.SimpleNode; - -/** - * Processor for SQL velocity templates. - * - * @see org.apache.cayenne.query.SQLTemplate - * @since 1.1 - */ -class SQLTemplateProcessor { - - private static RuntimeInstance sharedRuntime; - - static final String BINDINGS_LIST_KEY = "bindings"; - static final String RESULT_COLUMNS_LIST_KEY = "resultColumns"; - static final String HELPER_KEY = "helper"; - - private static final SQLTemplateRenderingUtils sharedUtils = new SQLTemplateRenderingUtils(); - - RuntimeInstance velocityRuntime; - SQLTemplateRenderingUtils renderingUtils; - - static { - initVelocityRuntime(); - } - - private static void initVelocityRuntime() { - // init static velocity engine - sharedRuntime = new RuntimeInstance(); - - // set null logger - sharedRuntime.addProperty( - RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, - new NullLogChute()); - - sharedRuntime.addProperty( - RuntimeConstants.RESOURCE_MANAGER_CLASS, - SQLTemplateResourceManager.class.getName()); - sharedRuntime.addProperty("userdirective", BindDirective.class.getName()); - sharedRuntime.addProperty("userdirective", BindEqualDirective.class.getName()); - sharedRuntime.addProperty("userdirective", BindNotEqualDirective.class.getName()); - sharedRuntime.addProperty("userdirective", BindObjectEqualDirective.class - .getName()); - sharedRuntime.addProperty("userdirective", BindObjectNotEqualDirective.class - .getName()); - sharedRuntime.addProperty("userdirective", ResultDirective.class.getName()); - sharedRuntime.addProperty("userdirective", ChainDirective.class.getName()); - sharedRuntime.addProperty("userdirective", ChunkDirective.class.getName()); - try { - sharedRuntime.init(); - } - catch (Exception ex) { - throw new CayenneRuntimeException( - "Error setting up Velocity RuntimeInstance.", - ex); - } - } - - SQLTemplateProcessor() { - this.velocityRuntime = sharedRuntime; - this.renderingUtils = sharedUtils; - } - - SQLTemplateProcessor(RuntimeInstance velocityRuntime, - SQLTemplateRenderingUtils renderingUtils) { - this.velocityRuntime = velocityRuntime; - this.renderingUtils = renderingUtils; - } - - /** - * Builds and returns a SQLStatement based on SQL template and a set of parameters. - * During rendering, VelocityContext exposes the following as variables: all - * parameters in the map, {@link SQLTemplateRenderingUtils} as a "helper" variable and - * SQLStatement object as "statement" variable. - */ - SQLStatement processTemplate(String template, Map<String, ?> parameters) throws Exception { - // have to make a copy of parameter map since we are gonna modify it.. - Map<String, Object> internalParameters = (parameters != null && !parameters.isEmpty()) ? new HashMap<String, Object>( - parameters) : new HashMap<String, Object>(5); - - List<ParameterBinding> bindings = new ArrayList<ParameterBinding>(); - List<ColumnDescriptor> results = new ArrayList<ColumnDescriptor>(); - internalParameters.put(BINDINGS_LIST_KEY, bindings); - internalParameters.put(RESULT_COLUMNS_LIST_KEY, results); - internalParameters.put(HELPER_KEY, renderingUtils); - - String sql = buildStatement(new VelocityContext(internalParameters), template); - - ParameterBinding[] bindingsArray = new ParameterBinding[bindings.size()]; - bindings.toArray(bindingsArray); - - ColumnDescriptor[] resultsArray = new ColumnDescriptor[results.size()]; - results.toArray(resultsArray); - - return new SQLStatement(sql, resultsArray, bindingsArray); - } - - String buildStatement(VelocityContext context, String template) - throws Exception { - // Note: this method is a reworked version of - // org.apache.velocity.app.Velocity.evaluate(..) - // cleaned up to avoid using any Velocity singletons - - StringWriter out = new StringWriter(template.length()); - SimpleNode nodeTree = null; - - try { - nodeTree = velocityRuntime.parse(new StringReader(template), template); - } - catch (ParseException pex) { - throw new CayenneRuntimeException("Error parsing template '" - + template - + "' : " - + pex.getMessage()); - } - - if (nodeTree == null) { - throw new CayenneRuntimeException("Error parsing template " + template); - } - - // ... not sure what InternalContextAdapter is for... - InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context); - ica.pushCurrentTemplateName(template); - - try { - nodeTree.init(ica, velocityRuntime); - nodeTree.render(ica, out); - return out.toString(); - } - finally { - ica.popCurrentTemplateName(); - } - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateRenderingUtils.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateRenderingUtils.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateRenderingUtils.java deleted file mode 100644 index 6350f56..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateRenderingUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import org.apache.cayenne.exp.Expression; - -/** - * Implements utility methods used inside Velocity templates - * when rendering SQLTemplates. - * - * @since 1.1 - */ -public class SQLTemplateRenderingUtils { - /** - * Returns the result of evaluation of expression with object. - */ - public Object cayenneExp(Object object, String expression) { - return Expression.fromString(expression).evaluate(object); - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManager.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManager.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManager.java deleted file mode 100644 index d339d71..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManager.java +++ /dev/null @@ -1,106 +0,0 @@ -/***************************************************************** - * 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.access.jdbc; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.Map; - -import org.apache.commons.collections.ExtendedProperties; -import org.apache.commons.collections.map.LRUMap; -import org.apache.velocity.Template; -import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.runtime.RuntimeServices; -import org.apache.velocity.runtime.resource.Resource; -import org.apache.velocity.runtime.resource.ResourceManager; -import org.apache.velocity.runtime.resource.loader.ResourceLoader; - -/** - * An implementation of the Velocity ResourceManager and ResourceLoader that - * creates templates from in-memory Strings. - * - * @since 1.1 - */ -// class must be public since it is instantiated by Velocity via reflection. -public class SQLTemplateResourceManager - extends ResourceLoader - implements ResourceManager { - - protected Map<String, Template> templateCache; - - public void initialize(RuntimeServices rs) throws Exception { - super.rsvc = rs; - this.templateCache = new LRUMap(100); - } - - public void clearCache() { - templateCache.clear(); - } - - /** - * Returns a Velocity Resource which is a Template for the given SQL. - */ - public Resource getResource(String resourceName, int resourceType, String encoding) - throws ResourceNotFoundException, ParseErrorException, Exception { - - synchronized (templateCache) { - Template resource = templateCache.get(resourceName); - - if (resource == null) { - resource = new Template(); - resource.setRuntimeServices(rsvc); - resource.setResourceLoader(this); - resource.setName(resourceName); - resource.setEncoding(encoding); - resource.process(); - - templateCache.put(resourceName, resource); - } - - return resource; - } - } - - public String getLoaderNameForResource(String resourceName) { - return getClass().getName(); - } - - @Override - public long getLastModified(Resource resource) { - return -1; - } - - @Override - public InputStream getResourceStream(String source) - throws ResourceNotFoundException { - return new ByteArrayInputStream(source.getBytes()); - } - - @Override - public void init(ExtendedProperties configuration) { - - } - - @Override - public boolean isSourceModified(Resource resource) { - return false; - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindDirective.java new file mode 100644 index 0000000..3ff81b7 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindDirective.java @@ -0,0 +1,176 @@ +/***************************************************************** + * 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.velocity; + +import java.io.IOException; +import java.io.Writer; +import java.util.Collection; +import java.util.Iterator; + +import org.apache.cayenne.access.jdbc.ParameterBinding; +import org.apache.cayenne.dba.TypesMapping; +import org.apache.cayenne.util.ConversionUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.parser.node.Node; + +/** + * A custom Velocity directive to create a PreparedStatement parameter text. There are the + * following possible invocation formats inside the template: + * + * <pre> + * #bind(value) - e.g. #bind($xyz) + * #bind(value jdbc_type_name) - e.g. #bind($xyz 'VARCHAR'). This is the most common and useful form. + * #bind(value jdbc_type_name, scale) - e.g. #bind($xyz 'VARCHAR' 2) + * </pre> + * <p> + * Other examples: + * </p> + * <p> + * <strong>Binding literal parameter value:</strong> + * </p> + * <p> + * <code>"WHERE SOME_COLUMN > #bind($xyz)"</code> produces + * <code>"WHERE SOME_COLUMN > ?"</code> and also places the value of the "xyz" parameter + * in the context "bindings" collection. + * </p> + * <p> + * <strong>Binding ID column of a DataObject value:</strong> + * </p> + * <p> + * <code>"WHERE ID_COL1 = #bind($helper.cayenneExp($xyz, 'db:ID_COL2')) + * AND ID_COL2 = #bind($helper.cayenneExp($xyz, 'db:ID_COL2'))"</code> produces <code>"WHERE ID_COL1 = ? AND ID_COL2 = ?"</code> and also places the + * values of id columns of the DataObject parameter "xyz" in the context "bindings" + * collection. + * </p> + * + * @since 1.1 + */ +public class BindDirective extends Directive { + + @Override + public String getName() { + return "bind"; + } + + @Override + public int getType() { + return LINE; + } + + /** + * Extracts the value of the object property to render and passes control to + * {@link #render(InternalContextAdapter, Writer, ParameterBinding)} to do the actual + * rendering. + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + throws IOException, ResourceNotFoundException, ParseErrorException, + MethodInvocationException { + + Object value = getChild(context, node, 0); + Object type = getChild(context, node, 1); + int scale = ConversionUtil.toInt(getChild(context, node, 2), -1); + String typeString = type != null ? type.toString() : null; + + if (value instanceof Collection) { + Iterator<?> it = ((Collection) value).iterator(); + while (it.hasNext()) { + render(context, writer, node, it.next(), typeString, scale); + + if (it.hasNext()) { + writer.write(','); + } + } + } + else { + render(context, writer, node, value, typeString, scale); + } + + return true; + } + + /** + * @since 3.0 + */ + protected void render( + InternalContextAdapter context, + Writer writer, + Node node, + Object value, + String typeString, + int scale) throws IOException, ParseErrorException { + + int jdbcType = TypesMapping.NOT_DEFINED; + if (typeString != null) { + jdbcType = TypesMapping.getSqlTypeByName(typeString); + } + else if (value != null) { + jdbcType = TypesMapping.getSqlTypeByJava(value.getClass()); + } else { + // value is null, set JDBC type to NULL + jdbcType = TypesMapping.getSqlTypeByName(TypesMapping.SQL_NULL); + } + + if (jdbcType == TypesMapping.NOT_DEFINED) { + throw new ParseErrorException("Can't determine JDBC type of binding (" + + value + + ", " + + typeString + + ") at line " + + node.getLine() + + ", column " + + node.getColumn()); + } + + render(context, writer, new ParameterBinding(value, jdbcType, scale)); + } + + protected void render( + InternalContextAdapter context, + Writer writer, + ParameterBinding binding) throws IOException { + + bind(context, binding); + writer.write('?'); + } + + protected Object getChild(InternalContextAdapter context, Node node, int i) + throws MethodInvocationException { + return (i >= 0 && i < node.jjtGetNumChildren()) ? node.jjtGetChild(i).value( + context) : null; + } + + /** + * Adds value to the list of bindings in the context. + */ + protected void bind(InternalContextAdapter context, ParameterBinding binding) { + + Collection bindings = (Collection) context.getInternalUserContext().get( + SQLTemplateProcessor.BINDINGS_LIST_KEY); + + if (bindings != null) { + bindings.add(binding); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java new file mode 100644 index 0000000..b773cf5 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java @@ -0,0 +1,57 @@ +/***************************************************************** + * 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.velocity; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.cayenne.access.jdbc.ParameterBinding; +import org.apache.velocity.context.InternalContextAdapter; + +/** + * A custom Velocity directive to create a PreparedStatement parameter text + * for "= ?". If null value is encountered, generated text will look like "IS NULL". + * Usage in Velocity template is "WHERE SOME_COLUMN #bindEqual($xyz)". + * + * @since 1.1 + */ +public class BindEqualDirective extends BindDirective { + + @Override + public String getName() { + return "bindEqual"; + } + + @Override + protected void render( + InternalContextAdapter context, + Writer writer, + ParameterBinding binding) + throws IOException { + + if (binding.getValue() != null) { + bind(context, binding); + writer.write("= ?"); + } + else { + writer.write("IS NULL"); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java new file mode 100644 index 0000000..a49a8d6 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java @@ -0,0 +1,56 @@ +/***************************************************************** + * 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.velocity; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.cayenne.access.jdbc.ParameterBinding; +import org.apache.velocity.context.InternalContextAdapter; + +/** + * A custom Velocity directive to create a PreparedStatement parameter text for "<>?". + * If null value is encountered, generated text will look like "IS NOT NULL". Usage in + * Velocity template is "WHERE SOME_COLUMN #bindNotEqual($xyz)". + * + * @since 1.1 + */ +public class BindNotEqualDirective extends BindDirective { + + @Override + public String getName() { + return "bindNotEqual"; + } + + @Override + protected void render( + InternalContextAdapter context, + Writer writer, + ParameterBinding binding) throws IOException { + + if (binding.getValue() != null) { + bind(context, binding); + writer.write("<> ?"); + } + else { + writer.write("IS NOT NULL"); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java new file mode 100644 index 0000000..ed1f7d0 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java @@ -0,0 +1,164 @@ +/***************************************************************** + * 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.velocity; + +import java.io.IOException; +import java.io.Writer; +import java.sql.Types; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.apache.cayenne.ObjectId; +import org.apache.cayenne.Persistent; +import org.apache.cayenne.access.jdbc.ParameterBinding; +import org.apache.cayenne.dba.TypesMapping; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.parser.node.Node; + +/** + * A custom Velocity directive to create a set of SQL conditions to match an ObjectId of + * an object. Usage in Velocity template is "WHERE #bindObjectEqual($object)" or "WHERE + * #bindObjectEqual($object $columns $idValues)". + * + * @since 3.0 + */ +public class BindObjectEqualDirective extends BindDirective { + + @Override + public String getName() { + return "bindObjectEqual"; + } + + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + throws IOException, ResourceNotFoundException, ParseErrorException, + MethodInvocationException { + + Object object = getChild(context, node, 0); + Map idMap = toIdMap(object); + + Object sqlColumns = getChild(context, node, 1); + Object idColumns = getChild(context, node, 2); + + if (idMap == null) { + // assume null object, and bind all null values + + if (sqlColumns == null || idColumns == null) { + throw new ParseErrorException("Invalid parameters. " + + "Either object has to be set " + + "or sqlColumns and idColumns or both."); + } + + idMap = Collections.EMPTY_MAP; + } + else if (sqlColumns == null || idColumns == null) { + // infer SQL columns from ID columns + sqlColumns = idMap.keySet().toArray(); + idColumns = sqlColumns; + } + + Object[] sqlColumnsArray = toArray(sqlColumns); + Object[] idColumnsArray = toArray(idColumns); + + if (sqlColumnsArray.length != idColumnsArray.length) { + throw new ParseErrorException( + "SQL columns and ID columns arrays have different sizes."); + } + + 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(context, writer, sqlColumnsArray[i], i); + writer.write(' '); + render(context, writer, new ParameterBinding(value, jdbcType, -1)); + } + + return true; + } + + protected Object[] toArray(Object columns) { + if (columns instanceof Collection) { + return ((Collection) columns).toArray(); + } + else if (columns.getClass().isArray()) { + return (Object[]) columns; + } + else { + return new Object[] { + columns + }; + } + } + + protected Map toIdMap(Object object) throws ParseErrorException { + if (object instanceof Persistent) { + return ((Persistent) object).getObjectId().getIdSnapshot(); + } + else if (object instanceof ObjectId) { + return ((ObjectId) object).getIdSnapshot(); + } + else if(object instanceof Map) { + return (Map) object; + } + else if (object != null) { + throw new ParseErrorException( + "Invalid object parameter, expected Persistent or ObjectId or null: " + + object); + } + else { + return null; + } + } + + protected void renderColumn( + InternalContextAdapter context, + Writer writer, + Object columnName, + int columnIndex) throws IOException { + + if (columnIndex > 0) { + writer.write(" AND "); + } + + writer.write(columnName.toString()); + } + + @Override + protected void render( + InternalContextAdapter context, + Writer writer, + ParameterBinding binding) throws IOException { + + if (binding.getValue() != null) { + bind(context, binding); + writer.write("= ?"); + } + else { + writer.write("IS NULL"); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java new file mode 100644 index 0000000..0b610ab --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java @@ -0,0 +1,70 @@ +/***************************************************************** + * 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.velocity; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.cayenne.access.jdbc.ParameterBinding; +import org.apache.velocity.context.InternalContextAdapter; + +/** + * A custom Velocity directive to create a set of SQL conditions to check unequality of an + * ObjectId of an object. Usage in Velocity template is "WHERE + * #bindObjectNotEqual($object)" or "WHERE #bindObjectNotEqual($object $columns + * $idValues)". + * + * @since 3.0 + */ +public class BindObjectNotEqualDirective extends BindObjectEqualDirective { + + @Override + public String getName() { + return "bindObjectNotEqual"; + } + + @Override + protected void renderColumn( + InternalContextAdapter context, + Writer writer, + Object columnName, + int columnIndex) throws IOException { + + if (columnIndex > 0) { + writer.write(" OR "); + } + + writer.write(columnName.toString()); + } + + @Override + protected void render( + InternalContextAdapter context, + Writer writer, + ParameterBinding binding) throws IOException { + + if (binding.getValue() != null) { + bind(context, binding); + writer.write("<> ?"); + } + else { + writer.write("IS NOT NULL"); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChainDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChainDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChainDirective.java new file mode 100644 index 0000000..b0be445 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChainDirective.java @@ -0,0 +1,112 @@ +/***************************************************************** + * 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.velocity; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.parser.node.ASTDirective; +import org.apache.velocity.runtime.parser.node.Node; + +/** + * A custom Velocity directive to conditionally join a number of {@link ChunkDirective chunks}. + * Usage of chain is the following: + * + * <pre> + * #chain(operator) - e.g. #chain(' AND ') + * #chain(operator prefix) - e.g. #chain(' AND ' 'WHERE ')</pre> + * + * <p><code>operator</code> (e.g. AND, OR, etc.) is used to join chunks that are included + * in a chain. <code>prefix</code> is inserted if a chain contains at least one chunk. + * </p> + * + * @since 1.1 + */ +public class ChainDirective extends Directive { + + @Override + public String getName() { + return "chain"; + } + + @Override + public int getType() { + return BLOCK; + } + + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + throws + IOException, + ResourceNotFoundException, + ParseErrorException, + MethodInvocationException { + + int size = node.jjtGetNumChildren(); + if (size == 0) { + return true; + } + + // BLOCK is the last child + Node block = node.jjtGetChild(node.jjtGetNumChildren() - 1); + String join = (size > 1) ? (String) node.jjtGetChild(0).value(context) : ""; + String prefix = (size > 2) ? (String) node.jjtGetChild(1).value(context) : ""; + + // if there is a conditional prefix, use a separate buffer ofr children + StringWriter childWriter = new StringWriter(30); + + int len = block.jjtGetNumChildren(); + int includedChunks = 0; + for (int i = 0; i < len; i++) { + Node child = block.jjtGetChild(i); + + // if this is a "chunk", evaluate its expression and prepend join if included... + if (child instanceof ASTDirective + && "chunk".equals(((ASTDirective) child).getDirectiveName())) { + + if (child.jjtGetNumChildren() < 2 + || child.jjtGetChild(0).value(context) != null) { + + if (includedChunks > 0) { + childWriter.write(join); + } + + includedChunks++; + } + } + + child.render(context, childWriter); + } + + if (includedChunks > 0) { + childWriter.flush(); + writer.write(prefix); + writer.write(childWriter.toString()); + } + + return true; + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChunkDirective.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChunkDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChunkDirective.java new file mode 100644 index 0000000..5ff0a5e --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChunkDirective.java @@ -0,0 +1,75 @@ +/***************************************************************** + * 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.velocity; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.parser.node.Node; + +/** + * A custom Velocity directive to describe a conditional chunk of a {@link ChainDirective chain}. + * Usage of chunk is the following: + * + * <pre> + * #chunk()...#end - e.g. #chunk()A = 5#end + * #chunk($paramKey)...#end - e.g. #chunk($a)A = $a#end + * </pre> + * <p> + * If context contains paramKey and it's value isn't null, chunk is included in the + * chain, and if it is not the first chunk, it is prefixed with chain join (OR/AND). + * If context doesn't contain paramKey or it's value is null, chunk is skipped. + * @since 1.1 + */ +public class ChunkDirective extends Directive { + + @Override + public String getName() { + return "chunk"; + } + + @Override + public int getType() { + return BLOCK; + } + + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + throws IOException, ResourceNotFoundException, ParseErrorException, + MethodInvocationException { + + // first child is an expression, second is BLOCK + if (node.jjtGetNumChildren() > 1 && node.jjtGetChild(0).value(context) == null) { + // skip this chunk + return false; + } + + // BLOCK is the last child + Node block = node.jjtGetChild(node.jjtGetNumChildren() - 1); + block.render(context, writer); + return true; + } + +}
